diff --git a/CHANGELOG.md b/CHANGELOG.md index c4d1476..bf13440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # RELEASE NOTES +## 2.0.4 (Jun 9, 2026) + +### Enhancements + +* Migrated to Go `1.25`. +* Updated vulnerable dependencies. + +### Fixes + +* Fixed issue that prevented installation of third-party CLI packages ([I#205](https://github.com/akamai/cli/issues/205)). +* Fixed issue that prevented installation of `cps` and `cloudlets` modules with Python `3.14+` ([I#214](https://github.com/akamai/cli/issues/214)). + ## 2.0.3 (Dec 10, 2025) ### Enhancements diff --git a/LICENSE b/LICENSE index 8dada3e..cf8ac33 100644 --- a/LICENSE +++ b/LICENSE @@ -186,10 +186,10 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright 2026 Akamai Technologies, Inc. All rights reserved. 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 these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 diff --git a/README.md b/README.md index bf4415c..0a7f30e 100644 --- a/README.md +++ b/README.md @@ -6,30 +6,20 @@ [![GoDoc](https://godoc.org/github.com/akamai/cli?status.svg)](https://pkg.go.dev/github.com/akamai/cli) -Use Akamai CLI to configure Akamai platform and products directly from the command line. You can install ready-to-use product packages or build your own custom solutions to manage from CLI. +Use the Akamai command-line interface (CLI) to configure Akamai's platform and products directly from the command line. You can install ready-to-use product packages or build your own custom solutions to manage from CLI. -### Benefits +## Benefits -- Simple and task-oriented interface -- Consistent user experience across all Akamai products -- Wide range of supported packages and capabilities -- Extend or build your own CLI packages with supported programming languages such as Go, Python, and JavaScript +- Simple and task-oriented interface. +- Consistent user experience across all Akamai products. +- Wide range of supported packages and capabilities. +- Extend or build your own CLI packages with supported programming languages such as Go, Python, and JavaScript. -### Available Packages - -Browse the list of [available packages](https://developer.akamai.com/cli). - -## Install Akamai CLI +## Install base Akamai CLI package Akamai CLI doesn't have any dependencies and is quick to install. However, you may need an additional runtime for packages depending on the programming language they are based on. -Install Akamai CLI by downloading a [release binary](https://github.com/akamai/cli/releases). See instructions for various operating systems. - -You can also use [Homebrew](#install-with-homebrew), [Docker](#install-with-docker), or compile from [source](#compile-from-source). - -### System dependencies for Python-based packages - -If you're using a Python-based CLI package, install these extra dependencies: +If you're using a Python-based CLI package, install these additional dependencies: - Python 3.3 or above - [Python 3 `pip` package installer](https://pip.pypa.io/en/stable/installation) @@ -38,30 +28,26 @@ If you're using a Python-based CLI package, install these extra dependencies: ### Install from binaries -Follow the instructions for your operating system. - -#### Linux and macOS +Download a [release binary](https://github.com/akamai/cli/releases) compatible with your operating system. -Once you download the appropriate binary for your system, make it executable, and optionally make it available in your `$PATH`. Run the following commands: +- **Linux and macOS.** Once you download the appropriate binary for your system, make it executable and move it to a directory you have write access to. Optionally, you can add the directory to your `$PATH` environment variable. Run these commands: -```sh -$ chmod +x ~/Downloads/akamai-- -$ mv ~/Downloads/akamai-- /usr/local/bin/akamai -``` - -#### Windows + ```sh + $ chmod +x ~/Downloads/akamai-- + $ mv ~/Downloads/akamai-- /usr/local/bin/akamai + ``` -Once you download the appropriate binary for your system, simply execute the binary from the command line. For example: +- **Windows.** Once you download the appropriate binary for your system, you can execute the binary from the command line. For example: -```sh -$ akamai.exe help -``` + ```sh + $ akamai.exe help + ``` ### Install with Homebrew You can also install Akamai CLI using the Homebrew package manager. If you haven’t used it before, check [Homebrew documentation](https://docs.brew.sh/Installation) for system requirements and read the installation guide. -Once set up, simply run: +Once set up, run this command: ```sh $ brew install akamai @@ -85,7 +71,7 @@ $ docker run -it -v $HOME/.edgerc:/root/.edgerc:ro akamai/shell The `akamai` command and basic packages are already installed. See the [akamai-docker](https://github.com/akamai/akamai-docker) repository for more details. -If you want to open Akamai Development Environment when calling the `akamai` command, add the following line to your `.bashrc`, `.bash_profile`, or `.zshrc` files: +If you want to open Akamai Development Environment when calling the `akamai` command, add this line to your `.bashrc`, `.bash_profile`, or `.zshrc` file: ```sh alias akamai='docker run -it -v $HOME/.edgerc:/root/.edgerc:ro akamai/shell' @@ -99,175 +85,168 @@ $ docker run -it -v $HOME/.akamai-cli:/cli/.akamai-cli akamai/shell This command installs the CLI and persists the configuration and packages in `$HOME/.akamai-docker` directory. -### Compile from Source - -**Prerequisite:** Make sure you install go 1.24.11 or later. +### Compile from source -To compile Akamai CLI from source: +To compile Akamai CLI from source, you need [Go](https://golang.org/) 1.25.7 or later installed. -1. Change the working directory: +1. Change the working directory. ```sh $ cd $GOPATH ``` -2. Fetch the package: +2. Fetch the package. ```sh $ git clone github.com/akamai/cli ``` -3. Go to the package directory: +3. Go to the package directory. ```sh $ cd cli ``` -4. Compile the binary: +4. Compile the binary. + + ```sh + # Linux, macOS, other Unix-based systems + go build -o akamai cli/main.go - - For Linux, macOS, and other Unix-based systems, run: `go build -o akamai cli/main.go` - - For Windows, run: `go build -o akamai.exe cli/main.go` + # Windows + go build -o akamai.exe cli/main.go + ``` 5. Move the `akamai` or `akamai.exe` binary so that it's available in your `$PATH`. -### API credentials +> **Tip:** Once you've installed the base CLI, you can expand the functionality by installing [CLI packages](https://github.com/akamai/?q=cli&type=&language=&sort=). + +## Authenticate Akamai-branded packages use a `.edgerc` file for standard EdgeGrid authentication. By default, CLI looks for credentials in your `$HOME` directory. You can override both the file location or the credentials section by passing the `--edgerc` or `--section` flags to each command. -To set up your `.edgerc` file, see [Get started with APIs](https://developer.akamai.com/api/getting-started#setup). - -## Upgrade +To set up your `.edgerc` file: -Unless you installed Akamai CLI with Homebrew, you can enable automatic check for updates when you run Akamai CLI v0.3.0 or later for the first time. +1. [Create authentication credentials](https://techdocs.akamai.com/developer/docs/edgegrid). -When run for the first time, CLI asks you to enable automatic upgrades. If you do not agree, `last-upgrade-check=ignore` is set in the `.akamai-cli/config` file (this option will still allow you to perform manual upgrade as explained below). Otherwise, if a new version is available, CLI prompts you to download it. Akamai CLI automatically checks the new version's `SHA256` signature to verify it is not corrupt. After the update, your original command executes using the new version. - -For information on manual upgrade and the supported Homebrew command, see `akamai upgrade` in [Built-in commands](#built-in-commands). +2. Place your credentials in an EdgeGrid resource file, `.edgerc`, under a heading of `[default]` at your local home directory. -## How to use Akamai CLI - -All CLI commands start with the `akamai` binary, followed by a command, and optionally an action or other arguments. - -```sh -akamai [global flags] [command] [action] [arguments...] -``` - -### Global flags - -Use following flags to modify the Akamai CLI behaviour or get additional information: - -- `--edgerc value, -e value` + ``` + [default] + client_secret = C113nt53KR3TN6N90yVuAgICxIRwsObLi0E67/N8eRN= + host = akab-h05tnam3wl42son7nktnlnnx-kbob3i3v.luna.akamaiapis.net + access_token = akab-acc35t0k3nodujqunph3w7hzp7-gtm6ij + client_token = akab-c113ntt0k3n4qtari252bfxxbsl-yvsdj + ``` - `akamai --edgerc ~/.edgerc2 ...` enables to use different configuration file than default `~/.edgerc` (in this case `~/.edgerc2`) +## Upgrade -- `--section value, -s value` +Unless you installed Akamai CLI with [Homebrew](#install-with-homebrew), you can enable automatic check for updates when you run Akamai CLI v0.3.0 or later for the first time. - `akamai --section cps ...` enables to use different section in configuration file than default `default` (in this case `cps`) +When run for the first time, the CLI asks if you want to enable automatic upgrades. If you don't agree, `last-upgrade-check=ignore` is set in the `.akamai-cli/config` file (this option will still allow you to perform a manual upgrade). Otherwise, if a new version is available, the CLI prompts you to download it. Akamai CLI automatically checks the new version's `SHA256` signature to verify it isn't corrupt. After the update, your original command executes using the new version. -- `--accountkey value, --account-key value` +For information on manual upgrade and the supported Homebrew command, see `akamai upgrade` in [Built-in commands](#built-in-commands). - `akamai --accountkey 1-ABCD:Z-XYZ ...` enables to use account switch key (in this case `1-ABCD:Z-XYZ`) +## Use -- `--help` +All CLI commands start with the `akamai` binary, followed by a command, and optionally an action or other arguments to further define the output. - `akamai --help` shows basic usage info and available commands +You can optionally provide the path to your `.edgerc` file and credentials section header. If you pass the command without the `--edgerc` and `--section` global flags, the command, by default, will point to the local home directory of your `.edgerc` file and the `default` credentials section header of that file. -- `--bash` +If you manage multiple accounts, pass your account switch key using the `--accountkey` global flag. - `akamai --bash` shows help on using auto-complete with bash +To use an installed command from the package you installed, run: -- `--zsh` +```sh +akamai [global flags] [command] [action] [arguments...] +``` - `akamai --zsh` shows help on using auto-complete with zsh +Example: -- `--proxy value` +```sh +akamai --edgerc "~/.edgerc" --section "default" --accountkey "A-CCT1234:A-CCT5432" property-manager new-property -p example.org -g grp_12345 -c ctr_C0NT7ACT -d prd_Web_App_Accel +``` - `akamai --proxy http://example.com:8080 ...` sets a proxy to use (in this case `http://example.com:8080`) +### Global flags -- `--version` +Use the global flags to modify the Akamai CLI behavior or get additional information. - `akamai --version` shows version number of currently installed Akamai CLI +| Flag | Description | +| ------ | --------- | +| `--edgerc` (string) | Alias `-e`. The location of your credentials file. The default is `$HOME/.edgerc`. | +| `--section` (string) | Alias `-s`. A credential set's section name. The default is `default`. | +| `--accountkey` (string) | Alias `--account-key`. An account switch key. | +| `--help` (boolean) | Outputs basic usage info and available commands. | +| `--bash` (boolean) | Outputs help on using auto-complete with bash. | +| `--zsh` (boolean) | Outputs help on using auto-complete with zsh. | +| `--proxy` (string) | Sets a proxy to use. | +| `--version` (boolean) | Outputs a version number of currently installed Akamai CLI. | ### Built-in commands -Use the following commands to manage packages and the toolkit: - -- `help` - - `akamai help` shows basic usage info and available commands. To learn more about a specific command, run `akamai help [sub-command]`. - -- `list` - - `akamai list` shows a list of available commands. If a command doesn't display, ensure the binary is executable and in your `$PATH`. - -- `install` - - This installs new packages from a git repository. - - `akamai install ` downloads and installs the command repository to the `$HOME/.akamai-cli` directory. - - For Github repositories, specify `user/repo` or `organization/repo`. For official Akamai packages, you can omit the `akamai/cli-` prefix. For example, to install `akamai/cli-property-manager`, it's enough to run `property-manager`. - - These examples all install Akamai CLI for Property Manager from Github using various aliases: - - ```sh +Use the built-in commands to manage packages and the toolkit. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandDescription
helpakamai help outputs basic usage info and available commands. To learn more about a specific command, run akamai help [sub-command].
listakamai list outputs a list of available commands. If a command doesn't display, ensure the binary is executable and in your $PATH.
installThis installs new packages from a git repository.

akamai install {package name or repository URL} downloads and installs the command repository to the $HOME/.akamai-cli directory.

For Github repositories, specify user/repo or organization/repo. For official Akamai packages, you can omit the akamai/cli- prefix. For example, to install akamai/cli-property-manager, run property-manager.

These examples install Akamai CLI for Property Manager from GitHub using various aliases:

+
     akamai install property-manager
     akamai install akamai/cli-property-manager
     akamai install https://github.com/akamai/cli-property-manager.git
-    ```
-
-    The `install` command accepts more than one argument, so you can install many packages at once using any of these types of syntax.
-
-- `uninstall`
-
-    To remove all the package files you installed with `akamai install`, run `akamai uninstall `, where `` is any command within that package.
-
-    The `uninstall` command accepts more than one argument, so you can uninstall many packages at once.
-
-- `update`
-
-    To update a package you installed with `akamai install`, run `akamai update `, where `` is any command within that package.
-
-    You can specify multiple packages to update at once.
-
-    If you don't specify additional arguments, `akamai update` updates _all_ packages installed with `akamai install`
-
-- `upgrade`
-
-    Manually upgrade Akamai CLI to the latest version.
-
-    If you installed Akamai CLI with Homebrew, run this command instead:
-
-    ```sh
-    $ brew upgrade akamai
-    ```
-
-- `search`
-
-    Search all the packages published on [developer.akamai.com](https://developer.akamai.com/) for the submitter string. Searches apply to the package name, alias, and description. Search results appear in the console output.
-
-- `config`
-
-    View or modify the configuration settings that drive the common CLI behavior. Akamai CLI maintains a local configuration file in its root directory. The `config` command supports these sub-commands:
-    - `get`
-    - `set`
-    - `list`
-    - `unset` or `rm`
-
-### Installed commands
-
-This commands depend on your installed packages. To use an installed command, run `akamai   [arguments]`, for example:
-
-```sh
-akamai property-manager new-property -p example.org -g grp_123456 -c ctr_X-XXXXXX -d prd_Web_App_Accel
-```
-For the list of supported commands, see the [documentation](https://developer.akamai.com/cli-packages) for each package.
+
+
The install command accepts more than one argument, so you can install many packages at once using any of these types of syntax.
uninstallTo remove all the package files you installed with akamai install, run akamai uninstall {command}, where {command} is any command within that package.

The uninstall command accepts more than one argument, so you can uninstall many packages at once.
updateTo update a package you installed with akamai install, run akamai update {command}, where {command} is any command within that package.

You can specify multiple packages to update at once. If you don't specify additional arguments, akamai update updates all packages installed with akamai install.
upgradeManually upgrade Akamai CLI to the latest version. If you installed Akamai CLI with Homebrew, run this command instead: brew upgrade akamai.
searchSearch all the packages published on Akamai GitHub for the submitter string. Searches apply to the package name, alias, and description. Search results appear in the console output.
configView or modify the configuration settings that drive the common CLI behavior. Akamai CLI maintains a local configuration file in its root directory. The config command supports these sub-commands: +
    +
  • get
  • +
  • set
  • +
  • set
  • +
  • unset or rm
  • +
+
### Custom commands -Akamai CLI provides a framework for writing custom CLI commands. See the extended [Akamai CLI documentation](https://developer.akamai.com/cli) to learn how to contribute, create custom packages, and build commands. +Akamai CLI provides a framework for writing custom CLI commands. Before you start to build your own commands, make sure you meet these prerequisites: @@ -281,7 +260,7 @@ As long as the result is executable, you can use any of the supported languages ### Logging -To see additional log information, prepend `AKAMAI_LOG=` to any CLI command. You can specify one of the following logging levels: +To see additional log information, prepend `AKAMAI_LOG=` to any CLI command. You can specify one of these logging levels: - `fatal` - `error` @@ -297,7 +276,7 @@ AKAMAI_LOG=debug akamai update property-manager Each level is a progressive superset of all previous tiers. The output for `debug` also includes `fatal`, `error`, `warn`, and `info` logs. -If you want to redirect logs to a file, use the `AKAMAI_CLI_LOG_PATH` environmental variable: +If you want to redirect logs to a file, use the `AKAMAI_CLI_LOG_PATH` environmental variable. ```sh AKAMAI_LOG=debug AKAMAI_CLI_LOG_PATH=akamai.log akamai update property-manager @@ -305,7 +284,7 @@ AKAMAI_LOG=debug AKAMAI_CLI_LOG_PATH=akamai.log akamai update property-manager ## Dependencies -Akamai CLI supports the following package managers that help you automatically install package dependencies: +Akamai CLI supports these package managers that help you automatically install package dependencies: - Python: `pip` (using `requirements.txt`) - Go: `go modules` @@ -319,25 +298,10 @@ The package you install needs a `cli.json` file. This is where you specify the c ### Format -- `requirements`: Specifies the runtime requirements. You may specify a minimum version number or use the `*` wildcard for any version. Possible requirements are: - - `go` - - `node` - - `python` - -- `commands`: Lists commands included in the package. - - `name`: The command name, used as the executable name. - - `aliases`: An array of aliases that invoke the same command. - - `version`: The command version. - - `description`: A short description for the command. - - `bin`: A URL to fetch a binary package from if it cannot be installed from source. - - The `bin` URL may contain the following placeholders: - - - `{{.Version}}`: The command version. - - `{{.Name}}`: The command name. - - `{{.OS}}`: The current operating system, either `windows`, `mac`, or `linux`. - - `{{.Arch}}`: The current OS architecture, either `386`, `amd64` or `arm64`. - - `{{.BinSuffix}}`: The binary suffix for the current OS: `.exe` for `windows`. +| Parameter | Description| +| ---------- | ---------- | +| `requirements` | Specifies the runtime requirements. You may specify a minimum version number or use the `*` wildcard for any version. Possible requirements are:
  • go
  • node
  • python
| +| `commands` | Lists commands included in the package. Contains:
  • name. The command name, used as the executable name.
  • aliases. An array of aliases that invoke the same command.
  • version. The command version.
  • description. A short description for the command.
  • bin. A URL to fetch a binary package from if it can't be installed from source. It may contain these placeholders:
    • {{.Version}}. The command version.
    • {{.Name}}. The command name.
    • {{.OS}}. The current operating system, either windows, mac, or linux.
    • {{.Arch}}. The current OS architecture, either 386, amd64, or arm64.
    • {{.BinSuffix}}. The binary suffix for the current OS: .exe for windows.
| ### Example @@ -356,14 +320,29 @@ The package you install needs a `cli.json` file. This is where you specify the c ] } ``` + ## Akamai CLI exit codes -When you complete an operation, Akamai CLI generates one of these exit codes: +When you complete an operation, Akamai CLI generates one of these exit codes. + +| Exit code | Description | +| ----------- | ----------- | +| `0` (Success) | Indicates that the latest command or script executed successfully. | +| `1` (Configuration error) | Indicates an error while loading `AKAMAI_CLI_VERSION` or `AKAMAI_CLI`. | +| `2` (Configuration error) | Indicates an error while creating the `cache directory`. | +| `3` (Configuration error) | Indicates an error while saving the `cache-path`. | +| `5` (Application error) | Indicates an error with the initial setup. Occurs when you run Akamai CLI for the first time.| +| `6` (Syntax error) | Indicates that the latest command or script can't be processed. | +| `7` (Syntax error) | Indicates that the commands in your installed packages have conflicting names. To fix this, add a prefix to the commands that have the same name. | + +## Reporting issues + +To report an issue or make a suggestion, create a new [GitHub issue](https://github.com/akamai/cli/issues). + +## License + +Copyright 2026 Akamai Technologies, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. -- `0` (Success) - Indicates that the latest command or script executed successfully. -- `1` (Configuration error) - Indicates an error while loading `AKAMAI_CLI_VERSION` or `AKAMAI_CLI`. -- `2` (Configuration error) - Indicates an error while creating the `cache directory`. -- `3` (Configuration error) - Indicates an error while saving the `cache-path`. -- `5` (Application error) - Indicates an error with the initial setup. Occurs when you run Akamai CLI for the first time. -- `6` (Syntax error) - Indicates that the latest command or script cannot be processed. -- `7` (Syntax error) - Indicates that the commands in your installed packages have conflicting names. To fix this, add a prefix to the commands that have the same name. +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. \ No newline at end of file diff --git a/cli/app/access_nix.go b/cli/app/access_nix.go index fe57317..872b7b7 100644 --- a/cli/app/access_nix.go +++ b/cli/app/access_nix.go @@ -1,19 +1,5 @@ //go:build !windows -// Copyright 2018. Akamai Technologies, Inc -// -// 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 app import "golang.org/x/sys/unix" diff --git a/cli/app/access_windows.go b/cli/app/access_windows.go index f083ac2..c1a1cfb 100644 --- a/cli/app/access_windows.go +++ b/cli/app/access_windows.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 app func checkWriteAccess(path string) error { diff --git a/cli/app/firstrun.go b/cli/app/firstrun.go index acf3e16..5fb3474 100644 --- a/cli/app/firstrun.go +++ b/cli/app/firstrun.go @@ -1,19 +1,5 @@ //go:build !nofirstrun -// Copyright 2018. Akamai Technologies, Inc -// -// 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 app import ( diff --git a/cli/app/firstrun_noinstall.go b/cli/app/firstrun_noinstall.go index 4d96642..b0d5a24 100644 --- a/cli/app/firstrun_noinstall.go +++ b/cli/app/firstrun_noinstall.go @@ -1,19 +1,5 @@ //go:build nofirstrun -// Copyright 2018. Akamai Technologies, Inc -// -// 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 app import ( diff --git a/pkg/apphelp/help_command.go b/pkg/apphelp/help_command.go index d9a462d..e396677 100644 --- a/pkg/apphelp/help_command.go +++ b/pkg/apphelp/help_command.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 apphelp import ( diff --git a/pkg/commands/command.go b/pkg/commands/command.go index 511a228..7697b44 100644 --- a/pkg/commands/command.go +++ b/pkg/commands/command.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( diff --git a/pkg/commands/command_config.go b/pkg/commands/command_config.go index cf9f683..9ee0a8b 100644 --- a/pkg/commands/command_config.go +++ b/pkg/commands/command_config.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( diff --git a/pkg/commands/command_install.go b/pkg/commands/command_install.go index c9cee91..ec8cda6 100644 --- a/pkg/commands/command_install.go +++ b/pkg/commands/command_install.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( @@ -19,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "net/url" "os" "path/filepath" "strings" @@ -37,9 +24,12 @@ import ( var ( thirdPartyDisclaimer = color.CyanString("Disclaimer: You are installing a third-party package, subject to its own terms and conditions. Akamai makes no warranty or representation with respect to the third-party package.") - githubRawURLTemplate = "https://raw.githubusercontent.com/akamai/%s/master/cli.json" ) +var buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) +} + func cmdInstall(git git.Repository, langManager packages.LangManager) cli.ActionFunc { return func(c *cli.Context) (e error) { start := time.Now() @@ -65,7 +55,16 @@ func cmdInstall(git git.Repository, langManager packages.LangManager) cli.Action oldCmds := getCommands(c) for _, repo := range c.Args().Slice() { - repo = tools.Githubize(repo) + repo = strings.TrimSpace(repo) + + if repo == "" { + return cli.Exit(color.RedString("Repository URL cannot be empty"), 1) + } + + if !strings.Contains(repo, "://") && !strings.HasPrefix(repo, "git@") { + repo = tools.Githubize(repo) + } + subCmd, err := installPackage(c.Context, git, langManager, repo) if err != nil { logger.Error(fmt.Sprintf("Error installing package: %v", err)) @@ -132,16 +131,29 @@ func installPackage(ctx context.Context, gitRepo git.Repository, langManager pac logger := log.FromContext(ctx) logger.Debug(fmt.Sprintf("Installing package from repository: %s", repo)) + repo = strings.TrimSpace(repo) + if repo == "" { + userMessage := "The provided repository URL is empty or contains only whitespace. " + + "Please specify a valid repository URL in a supported format " + + "(e.g., 'https://github.com//.git' or 'git@github.com:/')." + logger.Error(userMessage) + return nil, cli.Exit(color.RedString("%s", userMessage), 1) + } + srcPath, err := tools.GetAkamaiCliSrcPath() if err != nil { logger.Error(fmt.Sprintf("Unable to get akamai cli source path: %v", err)) return nil, err } - term := terminal.Get(ctx) - spin := term.Spinner() + owner, repoName := extractOwnerAndRepo(repo) + if owner == "" || repoName == "" { + msg := fmt.Sprintf("Unable to parse repository URL: %s", repo) + logger.Error(msg) + return nil, cli.Exit(color.RedString("Unable to install selected package"), 1) + } - dirName := strings.TrimSuffix(filepath.Base(repo), ".git") + dirName := repoName packageDir := filepath.Join(srcPath, dirName) if _, err = os.Stat(packageDir); err == nil { @@ -150,40 +162,70 @@ func installPackage(ctx context.Context, gitRepo git.Repository, langManager pac return nil, cli.Exit(color.YellowString("%s", warningMsg), 0) } + term := terminal.Get(ctx) + spin := term.Spinner() + spin.Start("Attempting to fetch package configuration from %s...", repo) - base := filepath.Base(dirName) - url := fmt.Sprintf(githubRawURLTemplate, base) - cmdPackage, err := readPackageFromGithub(url, dirName) - if err != nil { - spin.Stop(terminal.SpinnerStatusFail) - logger.Error(fmt.Sprintf("Failed to read package from github: %v", err)) - term.WriteError(err.Error()) + isOfficial := owner == "akamai" && strings.HasPrefix(repoName, "cli-") + + var cmdPackage subcommands - if strings.Contains(err.Error(), "404") { - return nil, cli.Exit(color.RedString("%s", tools.CapitalizeFirstWord(git.ErrPackageNotAvailable.Error())), 1) + if strings.HasPrefix(repo, "https://github.com/") { + var fetchErr error + for _, branch := range []string{"main", "master"} { + url := buildRawGitHubURL(owner, repoName, branch) + cmdPackage, fetchErr = readPackageFromGithub(url, dirName) + if fetchErr == nil { + break + } } - return nil, cli.Exit(color.RedString("%s", "Unable to install selected package"), 1) + if fetchErr != nil { + spin.Stop(terminal.SpinnerStatusFail) + logger.Error(fmt.Sprintf("Failed to read package from github: %v", fetchErr)) + term.WriteError(fetchErr.Error()) + + if strings.Contains(fetchErr.Error(), "404") { + userMessage := fmt.Sprintf(`%s + +Reason: +- The repository either does not exist, is private, or does not contain a 'cli.json' file. + +Steps to resolve this issue: +1. Verify that the URL is correct. +2. Ensure the repository is publicly accessible, or that you have the necessary permissions. +3. Confirm that the repository contains the required 'cli.json' configuration file. + +For a list of supported packages, visit: +%s +`, repo, "https://techdocs.akamai.com/home/page/products-tools-a-z") + + logger.Error(strings.TrimSpace(userMessage)) + term.WriteError(strings.TrimSpace(userMessage)) + + return nil, cli.Exit(color.RedString("%s", tools.CapitalizeFirstWord(git.ErrPackageNotAvailable.Error())), 1) + } + return nil, cli.Exit(color.RedString("Unable to install selected package"), 1) + } + spin.OK() } - spin.OK() - if isBinary(cmdPackage) { + if strings.HasPrefix(repo, "https://github.com/") && isBinary(cmdPackage) { logger.Debug(fmt.Sprintf("Installing binaries for package in directory: %s", packageDir)) ok, subCmd := installPackageBinaries(ctx, packageDir, cmdPackage, logger) if ok { return subCmd, nil } - // delete package directory if err := os.RemoveAll(packageDir); err != nil { logger.Error(fmt.Sprintf("Failed to remove package directory: %v", err)) return nil, err } - logger.Debug(fmt.Sprintf("Unable to install binaries for package in directory: %s, cloning repository: %s", packageDir, repo)) + logger.Debug(fmt.Sprintf("Unable to install binaries, cloning repository: %s", repo)) } spin.Start("Attempting to fetch command from %s...", repo) - if !strings.HasPrefix(repo, "https://github.com/akamai/cli-") && !strings.HasPrefix(repo, "git@github.com:akamai/cli-") { + if !isOfficial { term.Printf(color.CyanString("%s", thirdPartyDisclaimer)) } @@ -307,3 +349,39 @@ func installPackageBinaries(ctx context.Context, dir string, cmdPackage subcomma return true, &cmdPackage } + +// extractOwnerAndRepo parses a GitHub repo URL and returns the owner and repo name. +func extractOwnerAndRepo(repoURL string) (string, string) { + repoURL = strings.TrimSuffix(repoURL, ".git") + + // Handle SCP-style SSH: git@github.com:owner/repo + if strings.HasPrefix(repoURL, "git@") { + if i := strings.Index(repoURL, ":"); i != -1 { + path := repoURL[i+1:] + parts := strings.Split(strings.Trim(path, "/"), "/") + if len(parts) >= 2 { + return parts[0], parts[1] + } + } + return "", "" + } + + // Handle URL schemes (ssh://, https://, http://) + if u, err := url.Parse(repoURL); err == nil && u.Path != "" { + parts := strings.Split(strings.Trim(u.Path, "/"), "/") + if len(parts) >= 2 { + return parts[len(parts)-2], parts[len(parts)-1] + } + } + + // Handle local file URLs + if strings.HasPrefix(repoURL, "file://") { + localPath := strings.TrimPrefix(repoURL, "file://") + repoName := filepath.Base(localPath) + if repoName != "" { + return "local", repoName + } + } + + return "", "" +} diff --git a/pkg/commands/command_install_test.go b/pkg/commands/command_install_test.go index f1f4f50..44cc020 100644 --- a/pkg/commands/command_install_test.go +++ b/pkg/commands/command_install_test.go @@ -50,7 +50,9 @@ func TestCmdInstall(t *testing.T) { _, err = w.Write(configJSON) require.NoError(t, err) })) - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Start", "Attempting to fetch command from %s...", []interface{}{"https://github.com/akamai/cli-test-cmd.git"}).Return().Once() m.term.On("OK").Return().Once() m.term.On("Stop", terminal.SpinnerStatusFail).Return().Once() @@ -97,6 +99,9 @@ func TestCmdInstall(t *testing.T) { m.term.On("Printf", "\nSee \"%s\" for details.\n", []interface{}{color.BlueString("%s help [command]", tools.Self())}).Return().Once() }, teardown: func(t *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } require.NoError(t, os.RemoveAll(cliTestCmdRepo)) }, }, @@ -112,7 +117,9 @@ func TestCmdInstall(t *testing.T) { _, err = w.Write(configJSON) require.NoError(t, err) })) - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Start", "Attempting to fetch command from %s...", []interface{}{"https://github.com/akamai/cli-test-cmd.git"}).Return().Once() m.term.On("OK").Return().Once() m.term.On("Stop", terminal.SpinnerStatusFail).Return().Once() @@ -159,6 +166,9 @@ func TestCmdInstall(t *testing.T) { m.term.On("Printf", "\nSee \"%s\" for details.\n", []interface{}{color.BlueString("%s help [command]", tools.Self())}).Return().Once() }, teardown: func(t *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } require.NoError(t, os.RemoveAll(cliTestCmdRepo)) }, }, @@ -175,7 +185,9 @@ func TestCmdInstall(t *testing.T) { _, err = w.Write([]byte(output)) require.NoError(t, err) })) - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Start", "Attempting to fetch command from %s...", []interface{}{"https://github.com/akamai/cli-test-cmd.git"}).Return().Once() m.term.On("OK").Return().Once() m.term.On("Stop", terminal.SpinnerStatusFail).Return().Once() @@ -213,9 +225,57 @@ func TestCmdInstall(t *testing.T) { }, binaryResponseStatus: http.StatusOK, teardown: func(t *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } require.NoError(t, os.RemoveAll(cliTestCmdRepo)) }, }, + "install from third-party repository shows disclaimer": { + args: []string{"https://github.com/other-user/cli-other-pkg.git"}, + init: func(t *testing.T, m *mocked) { + targetDir := filepath.Join("testdata", ".akamai-cli", "src", "cli-other-pkg") + require.NoError(t, os.RemoveAll(targetDir)) + + m.term.On("Spinner").Return(m.term) + + h := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + configJSON := `{"commands": [{"name": "other-cmd"}]}` + _, err := w.Write([]byte(configJSON)) + require.NoError(t, err) + })) + buildRawGitHubURL = func(_, _, _ string) string { + return fmt.Sprintf("%s/cli.json", h.URL) + } + + m.term.On("Start", mock.Anything, mock.Anything).Return().Maybe() + m.term.On("OK").Return().Maybe() + m.term.On("Stop", mock.Anything).Return().Maybe() + + m.term.On("Printf", color.CyanString("%s", thirdPartyDisclaimer)).Return().Once() + + m.gitRepo.On("Clone", targetDir, "https://github.com/other-user/cli-other-pkg.git", false, m.term). + Return(nil). + Once(). + Run(func(_ mock.Arguments) { + err := os.MkdirAll(targetDir, 0755) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(targetDir, "cli.json"), []byte(`{"commands": []}`), 0644) + require.NoError(t, err) + }) + + m.langManager.On("Install", targetDir, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + + m.term.On("Writeln", mock.Anything).Return(0, nil).Maybe() + m.term.On("Printf", mock.Anything, mock.Anything).Return().Maybe() + }, + teardown: func(_ *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } + require.NoError(t, os.RemoveAll(filepath.Join("testdata", ".akamai-cli", "src", "cli-other-pkg"))) + }, + }, "package directory already exists": { args: []string{"installed"}, init: func(_ *testing.T, m *mocked) { @@ -242,7 +302,9 @@ func TestCmdInstall(t *testing.T) { _, err = w.Write(configJSON) require.NoError(t, err) })) - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Start", "Attempting to fetch command from %s...", []interface{}{"https://github.com/akamai/cli-test-cmd.git"}).Return().Once() m.term.On("OK").Return().Once() m.term.On("Stop", terminal.SpinnerStatusFail).Return().Once() @@ -256,6 +318,14 @@ func TestCmdInstall(t *testing.T) { m.gitRepo.On("Clone", filepath.Join("testdata", ".akamai-cli", "src", "cli-test-cmd"), "https://github.com/akamai/cli-test-cmd.git", false, m.term).Return(git2.ErrRepositoryAlreadyExists) }, + teardown: func(t *testing.T) { + // Reset the global function + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } + // Clean up the created test directory + require.NoError(t, os.RemoveAll(cliTestCmdRepo)) + }, withError: "Package is not available. Supported packages can be found here: https://techdocs.akamai.com/home/page/products-tools-a-z", }, "error reading downloaded package, invalid cli.json in repository": { @@ -270,7 +340,9 @@ func TestCmdInstall(t *testing.T) { _, err = w.Write(configJSON) require.NoError(t, err) })) - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Start", "Attempting to fetch command from %s...", []interface{}{"https://github.com/akamai/cli-test-invalid-json.git"}).Return().Once() m.term.On("OK").Return().Once() m.term.On("Stop", terminal.SpinnerStatusFail).Return().Once() @@ -288,6 +360,9 @@ func TestCmdInstall(t *testing.T) { m.term.On("WriteError", "unable to unmarshal package: invalid character 'i' looking for beginning of value").Return(0, nil).Once() }, teardown: func(t *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } require.NoError(t, os.RemoveAll(cliTestInvalidJSONRepo)) }, withError: "Unable to install selected package", @@ -304,7 +379,10 @@ func TestCmdInstall(t *testing.T) { _, err = w.Write(configJSON) require.NoError(t, err) })) - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" + // Override the URL builder for this test + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Start", "Attempting to fetch command from %s...", []interface{}{"https://github.com/akamai/cli-test-invalid-json.git"}).Return().Once() m.term.On("OK").Return().Once() m.term.On("Stop", terminal.SpinnerStatusFail).Return().Once() @@ -312,6 +390,9 @@ func TestCmdInstall(t *testing.T) { m.term.On("WriteError", "unable to unmarshal package: invalid character 'i' looking for beginning of value").Return(0, nil).Once() }, teardown: func(t *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } require.NoError(t, os.RemoveAll(cliTestInvalidJSONRepo)) }, withError: "Unable to install selected package", @@ -328,7 +409,10 @@ func TestCmdInstall(t *testing.T) { _, err = w.Write(configJSON) require.NoError(t, err) })) - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" + // Override the URL builder for this test + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Start", "Attempting to fetch command from %s...", []interface{}{"https://github.com/akamai/cli-test-cmd.git"}).Return().Once() m.term.On("OK").Return().Once() m.term.On("Stop", terminal.SpinnerStatusFail).Return().Once() @@ -378,6 +462,9 @@ func TestCmdInstall(t *testing.T) { m.term.On("Printf", "\nSee \"%s\" for details.\n", []interface{}{color.BlueString("%s help [command]", tools.Self())}).Return().Once() }, teardown: func(t *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } require.NoError(t, os.RemoveAll(cliTestCmdRepo)) }, }, @@ -394,8 +481,10 @@ func TestCmdInstall(t *testing.T) { _, err = w.Write([]byte(output)) require.NoError(t, err) })) - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" - + // Override the URL builder for this test + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Start", "Attempting to fetch command from %s...", []interface{}{"https://github.com/akamai/cli-test-cmd.git"}).Return().Once() m.term.On("OK").Return().Once() m.term.On("Stop", terminal.SpinnerStatusFail).Return().Once() @@ -449,6 +538,9 @@ func TestCmdInstall(t *testing.T) { }, binaryResponseStatus: http.StatusOK, teardown: func(t *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } require.NoError(t, os.RemoveAll(cliTestCmdRepo)) }, }, @@ -465,8 +557,10 @@ func TestCmdInstall(t *testing.T) { _, err = w.Write([]byte(output)) require.NoError(t, err) })) - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" - + // Override the URL builder for this test + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Start", "Attempting to fetch command from %s...", []interface{}{"https://github.com/akamai/cli-test-cmd.git"}).Return().Once() m.term.On("OK").Return().Once() m.term.On("Stop", terminal.SpinnerStatusFail).Return().Once() @@ -520,6 +614,9 @@ func TestCmdInstall(t *testing.T) { }, binaryResponseStatus: http.StatusNotFound, teardown: func(t *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } require.NoError(t, os.RemoveAll(cliTestCmdRepo)) }, }, @@ -537,8 +634,10 @@ func TestCmdInstall(t *testing.T) { require.NoError(t, err) })) - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" - + // Override the URL builder for this test + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Spinner").Return(m.term).Once() m.term.On("Start", "Installing Binaries...", []interface{}(nil)).Return().Once() m.term.On("Stop", terminal.SpinnerStatusWarn) @@ -569,10 +668,41 @@ func TestCmdInstall(t *testing.T) { m.term.On("WriteError", "oops").Return(0, nil).Once() }, teardown: func(t *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } require.NoError(t, os.RemoveAll(cliTestCmdRepo)) }, withError: "Unable to install selected package", }, + "package not found on github, 404 response": { + args: []string{"test-cmd"}, + init: func(_ *testing.T, m *mocked) { + m.term.On("Spinner").Return(m.term).Once() + m.term.On("Start", "Attempting to fetch package configuration from %s...", []interface{}{"https://github.com/akamai/cli-test-cmd.git"}).Return().Once() + + h := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } + + m.term.On("Stop", terminal.SpinnerStatusFail).Return().Once() + m.term.On("WriteError", mock.MatchedBy(func(s string) bool { + return strings.Contains(s, "invalid response status while fetching cli.json: 404") + })).Return(0, nil).Once() + m.term.On("WriteError", mock.MatchedBy(func(s string) bool { + return strings.Contains(s, "Steps to resolve this issue") + })).Return(0, nil).Once() + }, + teardown: func(_ *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } + }, + withError: "Package is not available. Supported packages can be found here: https://techdocs.akamai.com/home/page/products-tools-a-z", + }, } for name, test := range tests { @@ -615,3 +745,29 @@ func TestCmdInstall(t *testing.T) { }) } } + +func TestExtractOwnerAndRepo(t *testing.T) { + tests := []struct { + name string + url string + expectedOwner string + expectedRepo string + }{ + {"Standard HTTPS", "https://github.com/akamai/cli-terraform.git", "akamai", "cli-terraform"}, + {"HTTPS no suffix", "https://github.com/akamai/cli-terraform", "akamai", "cli-terraform"}, + {"SSH format", "git@github.com:akamai/cli-alerts.git", "akamai", "cli-alerts"}, + {"SSH no suffix", "git@github.com:custom-org/my-pkg", "custom-org", "my-pkg"}, + {"Deep path", "https://github.com/org/sub/repo.git", "sub", "repo"}, + {"Invalid URL", "not-a-url", "", ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + owner, repo := extractOwnerAndRepo(tt.url) + if owner != tt.expectedOwner || repo != tt.expectedRepo { + t.Errorf("extractOwnerAndRepo(%s) = (%s, %s); want (%s, %s)", + tt.url, owner, repo, tt.expectedOwner, tt.expectedRepo) + } + }) + } +} diff --git a/pkg/commands/command_list.go b/pkg/commands/command_list.go index a103415..bdadc00 100644 --- a/pkg/commands/command_list.go +++ b/pkg/commands/command_list.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( diff --git a/pkg/commands/command_search.go b/pkg/commands/command_search.go index 41eb6fa..36bdb79 100644 --- a/pkg/commands/command_search.go +++ b/pkg/commands/command_search.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( diff --git a/pkg/commands/command_subcommand.go b/pkg/commands/command_subcommand.go index 10d81de..558eacc 100644 --- a/pkg/commands/command_subcommand.go +++ b/pkg/commands/command_subcommand.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( diff --git a/pkg/commands/command_uninstall.go b/pkg/commands/command_uninstall.go index c1661a4..6578a8b 100644 --- a/pkg/commands/command_uninstall.go +++ b/pkg/commands/command_uninstall.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( diff --git a/pkg/commands/command_update.go b/pkg/commands/command_update.go index 11ad609..14a8768 100644 --- a/pkg/commands/command_update.go +++ b/pkg/commands/command_update.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( @@ -123,14 +109,28 @@ func updatePackage(ctx context.Context, gitRepo git.Repository, langManager pack packageVersions[command.Name] = command.Version } - repo := filepath.Base(repoDir) - url := fmt.Sprintf(githubRawURLTemplate, repo) + owner, repoName := extractOwnerAndRepo(tools.Githubize(cmd)) + if owner == "" || repoName == "" { + term.Spinner().Fail() + msg := fmt.Sprintf("Unable to parse repository URL: %s", repoDir) + logger.Error(msg) + term.WriteError(msg) + return cli.Exit("Unable to install selected package", 1) + } - remotePackage, err := readPackageFromGithub(url, repoDir) - if err != nil { + var remotePackage subcommands + var fetchErr error + for _, branch := range []string{"main", "master"} { + url := buildRawGitHubURL(owner, repoName, branch) + remotePackage, fetchErr = readPackageFromGithub(url, filepath.Base(repoDir)) + if fetchErr == nil { + break + } + } + if fetchErr != nil { term.Spinner().Fail() - logger.Error(fmt.Sprintf("Failed to read package from github: %v", err)) - return cli.Exit(color.RedString("unable to update, there was an issue with fetching latest configuration file: %v", err), 1) + logger.Error(fmt.Sprintf("Failed to read package from github: %v", fetchErr)) + return cli.Exit(color.RedString("unable to update, there was an issue with fetching latest configuration file: %v", fetchErr), 1) } remoteVersions := map[string]string{} @@ -252,6 +252,7 @@ func updateRepo(ctx context.Context, gitRepo git.Repository, logger *slog.Logger return cli.Exit(color.RedString("Unable to fetch updates: %v", err), 1) } } else { + term.Spinner().OK() logger.Debug(fmt.Sprintf("HEAD is the same as the remote: %s (old) vs %s (new)", refBeforePull.Hash().String(), ref.Hash().String())) debugMessage := fmt.Sprintf("command \"%s\" already up-to-date", cmd) logger.Warn(debugMessage) diff --git a/pkg/commands/command_update_test.go b/pkg/commands/command_update_test.go index 850f88e..2161571 100644 --- a/pkg/commands/command_update_test.go +++ b/pkg/commands/command_update_test.go @@ -104,6 +104,7 @@ func TestCmdUpdate(t *testing.T) { m.term.On("Spinner").Return(m.term).Once() m.term.On("WarnOK").Return().Once() m.term.On("Writeln", []interface{}{color.CyanString("command \"echo\" already up-to-date")}).Return(0, nil).Once() + m.term.On("OK").Return().Once() m.langManager.On("FindExec", packages.LanguageRequirements{Go: "1.14.0"}, cliEchoBin).Return([]string{cliEchoBin}, nil).Once() m.term.On("Spinner").Return(m.term).Once() m.term.On("OK").Return().Once() @@ -294,8 +295,9 @@ func TestCmdUpdate(t *testing.T) { _, err = w.Write(configJSON) require.NoError(t, err) })) - - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Spinner").Return(m.term).Once() m.term.On("Start", `Attempting to update "%s" command...`, []interface{}{"echo"}).Return().Once() @@ -308,6 +310,11 @@ func TestCmdUpdate(t *testing.T) { m.term.On("Spinner").Return(m.term).Once() m.term.On("OK").Return().Once() }, + teardown: func(_ *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } + }, }, "error opening repository, update from remote, success": { args: []string{"echo"}, @@ -323,8 +330,9 @@ func TestCmdUpdate(t *testing.T) { _, err = w.Write([]byte(output)) require.NoError(t, err) })) - - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Spinner").Return(m.term).Once() m.term.On("Start", `Attempting to update "%s" command...`, []interface{}{"echo"}).Return().Once() @@ -354,9 +362,11 @@ func TestCmdUpdate(t *testing.T) { }, teardown: func(t *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } require.NoError(t, os.RemoveAll(cliEchoRepo)) require.NoError(t, os.Rename(tempTestDir, cliEchoRepo)) - }, }, "error opening repository, update from remote, fail": { @@ -374,7 +384,9 @@ func TestCmdUpdate(t *testing.T) { require.NoError(t, err) })) - githubRawURLTemplate = h.URL + "/akamai/%s/master/cli.json" + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("%s/%s/%s/%s/cli.json", h.URL, owner, repo, branch) + } m.term.On("Spinner").Return(m.term).Once() m.term.On("Start", `Attempting to update "%s" command...`, []interface{}{"echo"}).Return().Once() @@ -406,6 +418,9 @@ func TestCmdUpdate(t *testing.T) { }, teardown: func(t *testing.T) { + buildRawGitHubURL = func(owner, repo, branch string) string { + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/cli.json", owner, repo, branch) + } require.NoError(t, os.RemoveAll(cliEchoRepo)) require.NoError(t, os.Rename(tempTestDir, cliEchoRepo)) }, diff --git a/pkg/commands/command_upgrade.go b/pkg/commands/command_upgrade.go index ffc50f5..c948ffd 100644 --- a/pkg/commands/command_upgrade.go +++ b/pkg/commands/command_upgrade.go @@ -1,19 +1,5 @@ //go:build !noautoupgrade -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( diff --git a/pkg/commands/command_upgrade_noop.go b/pkg/commands/command_upgrade_noop.go index 83bdd1d..798c8f4 100644 --- a/pkg/commands/command_upgrade_noop.go +++ b/pkg/commands/command_upgrade_noop.go @@ -1,19 +1,5 @@ //go:build noautoupgrade -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( diff --git a/pkg/commands/subcommands.go b/pkg/commands/subcommands.go index 0f9b60d..ef3fdaf 100644 --- a/pkg/commands/subcommands.go +++ b/pkg/commands/subcommands.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( diff --git a/pkg/commands/testdata/.akamai-cli/src/cli-echo-python/bin/akamai-echo-python b/pkg/commands/testdata/.akamai-cli/src/cli-echo-python/bin/akamai-echo-python index bfe214e..76efd14 100755 --- a/pkg/commands/testdata/.akamai-cli/src/cli-echo-python/bin/akamai-echo-python +++ b/pkg/commands/testdata/.akamai-cli/src/cli-echo-python/bin/akamai-echo-python @@ -1,19 +1,5 @@ #!/usr/bin/env python -# Copyright 2017 Akamai Technologies, Inc. All Rights Reserved -# -# 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. - import argparse import sys diff --git a/pkg/commands/upgrade.go b/pkg/commands/upgrade.go index 24c1edb..dc82c92 100644 --- a/pkg/commands/upgrade.go +++ b/pkg/commands/upgrade.go @@ -1,19 +1,5 @@ //go:build !noautoupgrade -// Copyright 2018. Akamai Technologies, Inc -// -// 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 commands import ( diff --git a/pkg/config/config.go b/pkg/config/config.go index f7a5696..2a973f1 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 ( diff --git a/pkg/log/log.go b/pkg/log/log.go index 0ad9435..3a16bd3 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 log import ( diff --git a/pkg/packages/golang.go b/pkg/packages/golang.go index 246646e..3bde44a 100644 --- a/pkg/packages/golang.go +++ b/pkg/packages/golang.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 packages import ( diff --git a/pkg/packages/javascript.go b/pkg/packages/javascript.go index 5ce01b6..afe864f 100644 --- a/pkg/packages/javascript.go +++ b/pkg/packages/javascript.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 packages import ( diff --git a/pkg/packages/php.go b/pkg/packages/php.go index 7875e93..6cbfba7 100644 --- a/pkg/packages/php.go +++ b/pkg/packages/php.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 packages import ( diff --git a/pkg/packages/python.go b/pkg/packages/python.go index 0f5f25e..e861084 100644 --- a/pkg/packages/python.go +++ b/pkg/packages/python.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 packages import ( @@ -36,7 +22,7 @@ var ( pythonVersionPattern = `Python ([2,3]\.\d+\.\d+).*` pythonVersionRegex = regexp.MustCompile(pythonVersionPattern) pipVersionPattern = `^pip \d{1,2}\..+ \(python [2,3]\.\d+\)` - venvHelpPattern = `usage: venv ` + venvHelpPattern = `usage: (python[\d.]* -m )?venv ` pipVersionRegex = regexp.MustCompile(pipVersionPattern) venvHelpRegex = regexp.MustCompile(venvHelpPattern) ) diff --git a/pkg/packages/python_test.go b/pkg/packages/python_test.go index ddbef24..b6d667e 100644 --- a/pkg/packages/python_test.go +++ b/pkg/packages/python_test.go @@ -125,6 +125,24 @@ usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear] ENV_DIR [ENV_DIR ...] venv: error: the following arguments are required: ENV_DIR ` + // Python 3.14+ changed the venv help prefix: when invoked as "python3" it shows + // "usage: python3 -m venv", and when invoked as "python3.14" it shows "usage: python3.14 -m venv". + py314VenvHelp := ` +usage: python3 -m venv [-h] [--system-site-packages] [--symlinks | --copies] + [--clear] [--upgrade] [--without-pip] [--prompt PROMPT] + [--upgrade-deps] [--without-scm-ignore-files] + ENV_DIR [ENV_DIR ...] +python3 -m venv: error: the following arguments are required: ENV_DIR +` + // actual output when running python3.14 -m venv (binary carries its own version number in usage) + py314VersionedVenvHelp := ` +usage: python3.14 -m venv [-h] [--system-site-packages] [--symlinks | --copies] + [--clear] [--upgrade] [--without-pip] [--prompt PROMPT] + [--upgrade-deps] [--without-scm-ignore-files] + ENV_DIR [ENV_DIR ...] +python3.14 -m venv: error: the following arguments are required: ENV_DIR +` + py314Version := "Python 3.14.2" activationScript := filepath.Join("veDir", "bin", "activate") activationScriptWin := filepath.Join("veDir", "Scripts", "activate.bat") py3BinWindows := filepath.Join("c:", "Program files", "Python", "python3.exe") @@ -225,6 +243,112 @@ venv: error: the following arguments are required: ENV_DIR }, true).Return(nil, nil).Once() }, }, + "with python 3.14 invoked as python3 (new venv help format) and pip, python 3 required": { + givenDir: srcDir, + veDir: veDir, + requiredPy: ver3, + goos: "linux", + init: func(m *mocked) { + m.On("LookPath", "python3").Return(py3Bin, nil).Once() + m.On("LookPath", "bash").Return(bashBin, nil).Times(3) + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "--version"}, + }, true).Return([]byte(py314Version), nil).Twice() + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "-m", "pip", "--version"}, + }, true).Return([]byte(py3PipVersion), nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "-m", "venv", "--version"}, + }, true).Return([]byte(py314VenvHelp), nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "-m", "ensurepip", "--upgrade"}, + }, true).Return(nil, nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "-m", "pip", "install", "--no-cache", "--upgrade", "pip", "setuptools"}, + }, true).Return(nil, nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "-m", "venv", "veDir"}, + Dir: "", + }, true).Return(nil, nil).Once() + m.On("GetOS").Return("linux").Times(4) + m.On("FileExists", veDir).Return(true, nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: bashBin, + Args: []string{"source", activationScript}, + Dir: "", + }, true).Return(nil, nil).Once() + m.On("FileExists", requirementsFile).Return(true, nil).Once() + m.On("FileExists", ".").Return(true, nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: py3VeBin, + Args: []string{py3VeBin, "-m", "pip", "install", "--upgrade", "--ignore-installed", "-r", requirementsFile}, + Dir: "", + }, true).Return(nil, nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: bashBin, + Args: []string{"deactivate"}, + }, true).Return(nil, nil).Once() + }, + }, + "with python 3.14 invoked as python3 but showing python3.14 in venv help, python 3 required": { + givenDir: srcDir, + veDir: veDir, + requiredPy: ver3, + goos: "linux", + init: func(m *mocked) { + m.On("LookPath", "python3").Return(py3Bin, nil).Once() + m.On("LookPath", "bash").Return(bashBin, nil).Times(3) + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "--version"}, + }, true).Return([]byte(py314Version), nil).Twice() + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "-m", "pip", "--version"}, + }, true).Return([]byte(py3PipVersion), nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "-m", "venv", "--version"}, + }, true).Return([]byte(py314VersionedVenvHelp), nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "-m", "ensurepip", "--upgrade"}, + }, true).Return(nil, nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "-m", "pip", "install", "--no-cache", "--upgrade", "pip", "setuptools"}, + }, true).Return(nil, nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: py3Bin, + Args: []string{py3Bin, "-m", "venv", "veDir"}, + Dir: "", + }, true).Return(nil, nil).Once() + m.On("GetOS").Return("linux").Times(4) + m.On("FileExists", veDir).Return(true, nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: bashBin, + Args: []string{"source", activationScript}, + Dir: "", + }, true).Return(nil, nil).Once() + m.On("FileExists", requirementsFile).Return(true, nil).Once() + m.On("FileExists", ".").Return(true, nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: py3VeBin, + Args: []string{py3VeBin, "-m", "pip", "install", "--upgrade", "--ignore-installed", "-r", requirementsFile}, + Dir: "", + }, true).Return(nil, nil).Once() + m.On("ExecCommand", &exec.Cmd{ + Path: bashBin, + Args: []string{"deactivate"}, + }, true).Return(nil, nil).Once() + }, + }, "with py 3.4 (windows) and pip, python 3 required": { givenDir: srcDir, veDir: veDir, diff --git a/pkg/packages/ruby.go b/pkg/packages/ruby.go index fab7bae..85b0cf3 100644 --- a/pkg/packages/ruby.go +++ b/pkg/packages/ruby.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 packages import ( diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index 25a32ae..ed0980d 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -1,17 +1,3 @@ -// Copyright 2020. Akamai Technologies, Inc -// -// 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 terminal import ( diff --git a/pkg/terminal/terminal_test.go b/pkg/terminal/terminal_test.go index 6d82b45..fe27478 100644 --- a/pkg/terminal/terminal_test.go +++ b/pkg/terminal/terminal_test.go @@ -1,17 +1,3 @@ -// Copyright 2020. Akamai Technologies, Inc -// -// 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 terminal import ( diff --git a/pkg/tools/util.go b/pkg/tools/util.go index 86698cd..cf0ea91 100644 --- a/pkg/tools/util.go +++ b/pkg/tools/util.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 tools import ( diff --git a/pkg/tools/util_test.go b/pkg/tools/util_test.go index 5cf37da..73d612a 100644 --- a/pkg/tools/util_test.go +++ b/pkg/tools/util_test.go @@ -1,17 +1,3 @@ -// Copyright 2018. Akamai Technologies, Inc -// -// 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 tools import ( diff --git a/pkg/version/version.go b/pkg/version/version.go index 619aeda..7fab5f8 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -4,7 +4,7 @@ import "github.com/Masterminds/semver" const ( // Version Application Version - Version = "2.0.3" + Version = "2.0.4" // Equals p1==p2 in version.Compare(p1, p2) Equals = 0 // Error failure parsing one of the parameters in version.Compare(p1, p2)