Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# RELEASE NOTES

## 2.0.5 (Jul 16, 2026)

### Enhancements

* Removed the `api-gateway` from the list of installable packages as the corresponding project was archived.
* Updated vulnerable dependencies.

## 2.0.4 (Jun 9, 2026)

### Enhancements
Expand Down
15 changes: 10 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/stretchr/testify v1.11.1
github.com/urfave/cli/v2 v2.19.3
golang.org/x/sys v0.43.0
golang.org/x/text v0.36.0
golang.org/x/sys v0.47.0
golang.org/x/text v0.40.0
)

require (
Expand Down Expand Up @@ -45,9 +45,14 @@ require (
github.com/stretchr/objx v0.5.3 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/term v0.45.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace (
github.com/yuin/goldmark v1.4.13 => github.com/yuin/goldmark v1.8.2 // Fix security vulnerability; can be removed once bumped by github.com/go-git/go-git/v5
golang.org/x/crypto v0.53.0 => golang.org/x/crypto v0.54.0 // Fix security vulnerability; can be removed once bumped by golang.org/x/net
)
22 changes: 11 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -115,21 +115,21 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg=
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand All @@ -143,19 +143,19 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
Expand Down
29 changes: 0 additions & 29 deletions pkg/commands/package_list/package-list.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,6 @@
"commands": [{"name":"adaptive-acceleration","aliases":["a2"],"description":"Reset A2 Push and Preconnect policy"}],
"requirements": {"python":"3.0.0"}
},
{
"title": "API Gateway",
"name": "api-gateway",
"url": "https://github.com/akamai/cli-api-gateway",
"issues": "https://github.com/akamai/cli-api-gateway/issues",
"commands": [
{
"name": "api-gateway",
"description": "Manage API definitions and endpoints",
"auto-complete": true,
"bin": "https://github.com/akamai/cli-api-gateway/releases/download/{{.Version}}/akamai-{{.Name}}-{{.Version}}-{{.OS}}{{.Arch}}{{.BinSuffix}}"
},
{
"name": "api-keys",
"description": "Manage API keys",
"auto-complete": true,
"bin": "https://github.com/akamai/cli-api-gateway/releases/download/{{.Version}}/akamai-{{.Name}}-{{.Version}}-{{.OS}}{{.Arch}}{{.BinSuffix}}"
},
{
"name": "api-security",
"description": "Manage API protections",
"auto-complete": true,
"bin": "https://github.com/akamai/cli-api-gateway/releases/download/{{.Version}}/akamai-{{.Name}}-{{.Version}}-{{.OS}}{{.Arch}}{{.BinSuffix}}"
}
],
"requirements": {
"go": "1.10.0"
}
},
{
"title": "Application Security",
"name": "appsec",
Expand Down
2 changes: 1 addition & 1 deletion pkg/packages/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (l *langManager) GetShell(goos string) (string, error) {
case "windows":
return "", nil
case "linux", "darwin":
sh, err := lookForBins(l.commandExecutor, "bash", "sh")
sh, err := lookForBins(l.commandExecutor, nil, "bash", "sh")
if err != nil && errors.As(err, &pathErr) && pathErr.Err != syscall.ENOENT {
return "", err
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/packages/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ func TestLangManager_FindExec(t *testing.T) {
givenCmdExec: "test",
init: func(m *mocked) {
m.On("LookPath", "python3").Return("/test/python", nil)
m.On("ExecCommand", &exec.Cmd{
Path: "/test/python",
Args: []string{"/test/python", "--version"},
}, true).Return([]byte("Python 3.9.0"), nil)
},
expected: []string{"/test/python", "test"},
},
Expand Down
47 changes: 35 additions & 12 deletions pkg/packages/python.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ func findPythonBin(ctx context.Context, cmdExecutor executor, ver, name string)
}()
if version.Compare("3.0.0", ver) != version.Greater {
// looking for python3 or py (windows)
bin, err = lookForBins(cmdExecutor, "python3", "python3.exe", "py.exe")
bin, err = lookForBins(cmdExecutor, verifyExecutable(cmdExecutor), "python3", "python3.exe", "py.exe")
if err != nil {
return "", fmt.Errorf("%w: %s. Please verify if the executable is included in your PATH", ErrRuntimeNotFound, "python 3")
}
Expand All @@ -430,14 +430,14 @@ func findPythonBin(ctx context.Context, cmdExecutor executor, ver, name string)
}
if version.Compare("2.0.0", ver) != version.Greater {
// looking for python2 or py (windows) - no virtualenv
bin, err = lookForBins(cmdExecutor, "python2", "python2.exe", "py.exe")
bin, err = lookForBins(cmdExecutor, nil, "python2", "python2.exe", "py.exe")
if err != nil {
return "", fmt.Errorf("%w: %s. Please verify if the executable is included in your PATH", ErrRuntimeNotFound, "python 2")
}
return bin, nil
}
// looking for any version
bin, err = lookForBins(cmdExecutor, "python2", "python", "python3", "py.exe", "python.exe")
bin, err = lookForBins(cmdExecutor, nil, "python2", "python", "python3", "py.exe", "python.exe")
if err != nil {
return "", fmt.Errorf("%w: %s. Please verify if the executable is included in your PATH", ErrRuntimeNotFound, "python")
}
Expand All @@ -456,12 +456,12 @@ func findPipBin(ctx context.Context, cmdExecutor executor, requiredPy string) (s
}()
switch version.Compare(requiredPy, "3.0.0") {
case version.Greater, version.Equals:
bin, err = lookForBins(cmdExecutor, "pip3", "pip3.exe")
bin, err = lookForBins(cmdExecutor, nil, "pip3", "pip3.exe")
if err != nil {
return "", fmt.Errorf("%w: %s", ErrPackageManagerNotFound, "pip3")
}
case version.Smaller:
bin, err = lookForBins(cmdExecutor, "pip2")
bin, err = lookForBins(cmdExecutor, nil, "pip2")
if err != nil {
return "", fmt.Errorf("%w, %s", ErrPackageManagerNotFound, "pip2")
}
Expand Down Expand Up @@ -498,14 +498,37 @@ func installPythonDepsPip(ctx context.Context, cmdExecutor executor, bin, dir st
return nil
}

func lookForBins(cmdExecutor executor, bins ...string) (string, error) {
var err error
var bin string
// lookForBins searches for the first available binary from the provided list using LookPath.
// If verify is non-nil, it is called with the resolved binary path; binaries for which
// verify returns false are skipped. Pass nil to skip verification entirely.
func lookForBins(cmdExecutor executor, verify func(string) bool, bins ...string) (string, error) {
var lastErr error
for _, binName := range bins {
bin, err = cmdExecutor.LookPath(binName)
if err == nil {
return bin, nil
bin, err := cmdExecutor.LookPath(binName)
if err != nil {
lastErr = err
continue
}
if verify != nil && !verify(bin) {
// Binary exists in PATH but failed verification - skip silently.
// On Windows this catches App Execution Alias stubs that open the
// Microsoft Store instead of running Python (exit status 9009).
continue
}
return bin, nil
}
if lastErr == nil {
lastErr = fmt.Errorf("not found")
}
return "", lastErr
}

// verifyExecutable returns a verifier function that checks whether a binary can actually
// be executed (exits without error).
func verifyExecutable(cmdExecutor executor) func(string) bool {
return func(bin string) bool {
cmd := exec.Command(bin, "--version")
_, err := cmdExecutor.ExecCommand(cmd, true)
return err == nil
}
return bin, err
}
42 changes: 35 additions & 7 deletions pkg/packages/python_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ python3.14 -m venv: error: the following arguments are required: ENV_DIR
m.On("ExecCommand", &exec.Cmd{
Path: py3Bin,
Args: []string{py3Bin, "--version"},
}, true).Return([]byte(py34Version), nil).Twice()
}, true).Return([]byte(py34Version), nil).Times(3)
m.On("ExecCommand", &exec.Cmd{
Path: py3Bin,
Args: []string{py3Bin, "-m", "pip", "--version"},
Expand Down Expand Up @@ -254,7 +254,7 @@ python3.14 -m venv: error: the following arguments are required: ENV_DIR
m.On("ExecCommand", &exec.Cmd{
Path: py3Bin,
Args: []string{py3Bin, "--version"},
}, true).Return([]byte(py314Version), nil).Twice()
}, true).Return([]byte(py314Version), nil).Times(3)
m.On("ExecCommand", &exec.Cmd{
Path: py3Bin,
Args: []string{py3Bin, "-m", "pip", "--version"},
Expand Down Expand Up @@ -307,7 +307,7 @@ python3.14 -m venv: error: the following arguments are required: ENV_DIR
m.On("ExecCommand", &exec.Cmd{
Path: py3Bin,
Args: []string{py3Bin, "--version"},
}, true).Return([]byte(py314Version), nil).Twice()
}, true).Return([]byte(py314Version), nil).Times(3)
m.On("ExecCommand", &exec.Cmd{
Path: py3Bin,
Args: []string{py3Bin, "-m", "pip", "--version"},
Expand Down Expand Up @@ -364,7 +364,7 @@ python3.14 -m venv: error: the following arguments are required: ENV_DIR
m.On("ExecCommand", &exec.Cmd{
Path: py3BinWindows,
Args: []string{py3BinWindows, "--version"},
}, true).Return([]byte(py34Version), nil).Twice()
}, true).Return([]byte(py34Version), nil).Times(3)
m.On("ExecCommand", &exec.Cmd{
Path: py3BinWindows,
Args: []string{py3BinWindows, "-m", "venv", "--version"},
Expand Down Expand Up @@ -417,7 +417,7 @@ python3.14 -m venv: error: the following arguments are required: ENV_DIR
m.On("ExecCommand", &exec.Cmd{
Path: py3BinWindows,
Args: []string{py3BinWindows, "--version"},
}, true).Return([]byte(py310Version), nil).Twice()
}, true).Return([]byte(py310Version), nil).Times(3)
m.On("ExecCommand", &exec.Cmd{
Path: py3BinWindows,
Args: []string{py3BinWindows, "-m", "venv", "--version"},
Expand Down Expand Up @@ -507,7 +507,7 @@ python3.14 -m venv: error: the following arguments are required: ENV_DIR
m.On("ExecCommand", &exec.Cmd{
Path: py3Bin,
Args: []string{py3Bin, "--version"},
}, true).Return([]byte{}, nil).Once()
}, true).Return([]byte{}, nil).Twice()
},
withError: fmt.Errorf("unable to validate python dependency: unable to determine installed version, minimum version required: python: /test/python3 --version"),
},
Expand All @@ -520,7 +520,7 @@ python3.14 -m venv: error: the following arguments are required: ENV_DIR
m.On("ExecCommand", &exec.Cmd{
Path: py3Bin,
Args: []string{py3Bin, "--version"},
}, true).Return([]byte(py34Version), nil).Once()
}, true).Return([]byte(py34Version), nil).Twice()
},
withError: fmt.Errorf("unable to validate python dependency: higher version is required to install this command: required: /test/python3:3.5.5, have: 3.4.0. Please install the required Python branch"),
},
Expand Down Expand Up @@ -548,6 +548,34 @@ python3.14 -m venv: error: the following arguments are required: ENV_DIR
},
withError: fmt.Errorf("unable to validate python dependency: unable to locate runtime: Please install the following Python branch: 2.0.0"),
},
"python3.exe is Windows App Execution Alias stub, py.exe is used as fallback": {
givenDir: srcDir,
veDir: veDir,
requiredPy: ver3,
goos: "windows",
init: func(m *mocked) {
windowsStubPath := filepath.Join("C:", "Users", "user", "AppData", "Local", "Microsoft", "WindowsApps", "python3.exe")
m.On("LookPath", "python3").Return("", errors.New("not found")).Once()
m.On("LookPath", "python3.exe").Return(windowsStubPath, nil).Once()
// The Windows App Execution Alias stub fails with exit status 9009
m.On("ExecCommand", &exec.Cmd{
Path: windowsStubPath,
Args: []string{windowsStubPath, "--version"},
}, true).Return([]byte{}, errors.New("exit status 9009")).Once()
// Fall back to py.exe which is the actual Python installation
m.On("LookPath", "py.exe").Return(py3BinWindows, nil).Once()
m.On("ExecCommand", &exec.Cmd{
Path: py3BinWindows,
Args: []string{py3BinWindows, "--version"},
}, true).Return([]byte(py310Version), nil).Twice()
// pip check fails to keep mock setup minimal
m.On("ExecCommand", &exec.Cmd{
Path: py3BinWindows,
Args: []string{py3BinWindows, "-m", "pip", "--version"},
}, true).Return([]byte{}, errors.New("pip not installed")).Once()
},
withError: fmt.Errorf("unable to validate python dependency: pip not found. Please verify your setup: "),
},
}

for name, test := range tests {
Expand Down
2 changes: 1 addition & 1 deletion pkg/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "github.com/Masterminds/semver"

const (
// Version Application Version
Version = "2.0.4"
Version = "2.0.5"
// Equals p1==p2 in version.Compare(p1, p2)
Equals = 0
// Error failure parsing one of the parameters in version.Compare(p1, p2)
Expand Down
Loading