1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-06-15 20:37:43 +00:00

[PR #12152/b34ef22c backport][stable-13] add go module to manage Go packages via go install (#12269)

add go module to manage Go packages via go install (#12152)

* add golang_package module to manage Go packages via go install

Adds a new module to install, update, and remove Go packages
using go install. Supports inline version pinning in package
names (e.g. pkg/tool/cmd with version suffix).

Assisted-by: Claude Opus 4.6


* fix copyright year and BOTMETA alphabetical order

* fix environment keyword docs, add integration test aliases

- Use C(environment) instead of O(ignore:environment) for task keyword
- Add tests/integration/targets/golang_package/aliases for CI
- Fix setup.yml: handle Alpine/ArchLinux/FreeBSD package names, skip Go < 1.16
- Pin tests to x/tools v0.24.1 (only version compatible with Go 1.16-1.25)



* test signing trace



---------



(cherry picked from commit b34ef22c82)

Co-authored-by: Shreyash <shrbhosa@redhat.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
patchback[bot] 2026-06-14 12:33:01 +02:00 committed by GitHub
parent 9e690837bd
commit 3cba1cd735
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 436 additions and 0 deletions

2
.github/BOTMETA.yml vendored
View file

@ -676,6 +676,8 @@ files:
maintainers: pixslx
$modules/gitlab_project_approvals.py:
maintainers: masa-orca
$modules/golang_package.py:
maintainers: shrbhosa
$modules/grove.py:
maintainers: zimbatm
$modules/gunicorn.py:

View file

@ -0,0 +1,291 @@
#!/usr/bin/python
# Copyright (c) 2026 Shreyash Bhosale <shreyashpb16@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import annotations
DOCUMENTATION = r"""
module: golang_package
short_description: Manage Go packages with C(go install)
version_added: 13.1.0
description:
- Manage Go packages with C(go install).
- Packages are installed as binaries into the Go binary directory (E(GOBIN)).
- Uninstalling a package removes its binary from E(GOBIN).
author: "Shreyash Bhosale (@shrbhosa)"
extends_documentation_fragment:
- community.general._attributes
attributes:
check_mode:
support: full
diff_mode:
support: none
options:
executable:
description:
- Path to the C(go) binary.
- If not specified, the module looks for C(go) in E(PATH).
type: path
name:
description:
- The name of a Go package to install.
- Must be a full Go package path, for example V(golang.org/x/tools/cmd/stringer).
- A version can be specified inline using the V(@) suffix, for example
V(golang.org/x/tools/cmd/stringer@v0.29.0). This allows pinning different
versions when installing multiple packages.
- The inline V(@version) syntax cannot be combined with the O(version) parameter.
type: list
elements: str
required: true
version:
description:
- The version to install, for example V(v1.55.2).
- The version must include the V(v) prefix as required by Go module versioning.
- Can only be used when O(name) contains a single package without an inline V(@version).
- When not specified and O(state=present), the latest version is installed for new packages.
type: str
state:
description:
- The desired state of the Go package.
type: str
default: present
choices: ["present", "absent", "latest"]
requirements:
- Go >= 1.16
notes:
- The Go binary directory is determined by C(go env GOBIN). To customize the install location,
set the E(GOBIN) environment variable using the task's C(environment) directive.
- Go does not provide a native uninstall command. When O(state=absent), the module removes the
binary file from E(GOBIN) directly.
"""
EXAMPLES = r"""
- name: Install stringer
community.general.golang_package:
name: golang.org/x/tools/cmd/stringer
- name: Install golangci-lint at a specific version
community.general.golang_package:
name: github.com/golangci/golangci-lint/cmd/golangci-lint
version: v1.55.2
- name: Install multiple packages at specific versions
community.general.golang_package:
name:
- golang.org/x/tools/cmd/stringer@v0.29.0
- github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
- name: Install multiple packages
community.general.golang_package:
name:
- golang.org/x/tools/cmd/stringer
- golang.org/x/tools/cmd/goimports
- name: Remove stringer
community.general.golang_package:
name: golang.org/x/tools/cmd/stringer
state: absent
- name: Update golangci-lint to the latest version
community.general.golang_package:
name: github.com/golangci/golangci-lint/cmd/golangci-lint
state: latest
- name: Install into a custom directory
community.general.golang_package:
name: golang.org/x/tools/cmd/stringer
environment:
GOBIN: /usr/local/bin
"""
import json
import os
from ansible.module_utils.basic import AnsibleModule
class Go:
def __init__(self, module, **kwargs):
self.module = module
self.executable = [kwargs["executable"] or module.get_bin_path("go", True)]
self.state = kwargs["state"]
self.version = kwargs["version"]
self._gobin = None
self.packages = []
for name in kwargs["name"]:
if "@" in name:
pkg, ver = name.rsplit("@", 1)
self.packages.append((pkg, ver))
else:
self.packages.append((name, None))
@property
def package_names(self):
return [pkg for pkg, dummy in self.packages]
def _exec(self, args, run_in_check_mode=False, check_rc=True):
if not self.module.check_mode or run_in_check_mode:
cmd = self.executable + args
rc, out, err = self.module.run_command(cmd, check_rc=check_rc)
return out, err
return "", ""
def _get_gobin(self):
if self._gobin is not None:
return self._gobin
out, dummy = self._exec(["env", "GOBIN"], run_in_check_mode=True, check_rc=True)
gobin = out.strip()
if gobin:
self._gobin = gobin
return self._gobin
out, dummy = self._exec(["env", "GOPATH"], run_in_check_mode=True, check_rc=True)
gopath = out.strip()
if not gopath:
self.module.fail_json(msg="Could not determine Go binary directory: GOBIN and GOPATH are both empty")
self._gobin = os.path.join(gopath.split(os.pathsep)[0], "bin")
return self._gobin
def _get_binary_info(self, binary_path):
out, dummy = self._exec(["version", "-m", binary_path], run_in_check_mode=True, check_rc=False)
if not out:
return None
path = None
version = None
mod_path = None
for line in out.splitlines():
parts = line.strip().split("\t")
if len(parts) >= 2 and parts[0] == "path":
path = parts[1]
elif len(parts) >= 3 and parts[0] == "mod":
mod_path = parts[1]
version = parts[2]
if path and version:
return {"path": path, "version": version, "mod_path": mod_path}
return None
def get_installed(self):
gobin = self._get_gobin()
installed = {}
for pkg_name in self.package_names:
binary_name = pkg_name.rsplit("/", 1)[-1]
binary_path = os.path.join(gobin, binary_name)
if not os.path.isfile(binary_path):
continue
info = self._get_binary_info(binary_path)
if info and info["path"] == pkg_name:
installed[pkg_name] = info["version"]
return installed
def _get_latest_version(self, mod_path):
out, dummy = self._exec(
["list", "-m", "-json", mod_path + "@latest"],
run_in_check_mode=True,
check_rc=False,
)
if out:
try:
data = json.loads(out)
return data.get("Version")
except (json.JSONDecodeError, ValueError):
pass
return None
def is_outdated(self, package):
gobin = self._get_gobin()
binary_name = package.rsplit("/", 1)[-1]
binary_path = os.path.join(gobin, binary_name)
info = self._get_binary_info(binary_path)
if not info or info["path"] != package:
return True
installed_version = info["version"]
mod_path = info.get("mod_path")
if not mod_path:
self.module.fail_json(msg=f"Could not determine module path for package {package}")
latest_version = self._get_latest_version(mod_path)
if not latest_version:
self.module.fail_json(msg=f"Could not determine latest version for package {package}")
return installed_version != latest_version
def install(self, packages):
by_version = {}
for pkg, ver in packages:
effective = ver or self.version or "latest"
by_version.setdefault(effective, []).append(pkg)
out_all, err_all = "", ""
for ver, pkgs in by_version.items():
cmd = ["install"] + [p + "@" + ver for p in pkgs]
out, err = self._exec(cmd)
out_all += out
err_all += err
return out_all, err_all
def uninstall(self, packages):
gobin = self._get_gobin()
for package in packages:
binary_name = package.rsplit("/", 1)[-1]
binary_path = os.path.join(gobin, binary_name)
if os.path.isfile(binary_path) and not self.module.check_mode:
os.remove(binary_path)
def main():
arg_spec = dict(
executable=dict(type="path"),
name=dict(required=True, type="list", elements="str"),
state=dict(default="present", choices=["present", "absent", "latest"]),
version=dict(type="str"),
)
module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)
name = module.params["name"]
state = module.params["state"]
version = module.params["version"]
has_inline_version = any("@" in n for n in name)
if version and has_inline_version:
module.fail_json(msg="Cannot combine the 'version' parameter with inline @version in package names")
if version and len(name) > 1:
module.fail_json(msg="The 'version' parameter can only be used when 'name' contains a single package")
if state == "latest" and (version or has_inline_version):
module.fail_json(msg="Cannot specify a version when state=latest")
module.run_command_environ_update = dict(LANGUAGE="C", LC_ALL="C")
go = Go(module, **module.params)
changed, out, err = False, None, None
installed_packages = go.get_installed()
if state == "present":
to_install = []
for pkg, ver in go.packages:
effective_ver = ver or version
if pkg not in installed_packages:
to_install.append((pkg, ver))
elif effective_ver and effective_ver != installed_packages[pkg]:
to_install.append((pkg, ver))
if to_install:
changed = True
out, err = go.install(to_install)
elif state == "latest":
to_update = [(pkg, ver) for pkg, ver in go.packages if pkg not in installed_packages or go.is_outdated(pkg)]
if to_update:
changed = True
out, err = go.install(to_update)
else: # absent
to_uninstall = [pkg for pkg in go.package_names if pkg in installed_packages]
if to_uninstall:
changed = True
go.uninstall(to_uninstall)
module.exit_json(changed=changed, stdout=out, stderr=err)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,6 @@
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
azp/posix/2
destructive

View file

@ -0,0 +1,7 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
dependencies:
- setup_pkg_mgr

View file

@ -0,0 +1,10 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- import_tasks: setup.yml
- block:
- import_tasks: test_general.yml
- import_tasks: test_version.yml
when: has_go | default(false)

View file

@ -0,0 +1,27 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- name: Determine Go package name
set_fact:
golang_package_name: >-
{{ 'go' if ansible_facts.os_family in ['Alpine', 'Archlinux', 'FreeBSD'] else 'golang' }}
- block:
- name: Install Go
package:
name: "{{ golang_package_name }}"
state: present
- name: Check Go version
command: go version
register: go_version_output
changed_when: false
- name: Set has_go if Go >= 1.19
set_fact:
has_go: true
when: (go_version_output.stdout | regex_search('go(\d+\.\d+)', '\\1') | first) is version('1.19', '>=')
when:
- ansible_facts.distribution != 'MacOSX'

View file

@ -0,0 +1,46 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# x/tools v0.24.1 is the only version that works on both Go 1.16-1.24 AND Go 1.25+.
# See https://github.com/golang/go/issues/74462 for details.
- name: Ensure stringer is absent
community.general.golang_package:
state: absent
name: golang.org/x/tools/cmd/stringer
register: uninstall_absent
- name: Install stringer
community.general.golang_package:
name: golang.org/x/tools/cmd/stringer
version: v0.24.1
register: install_result
- name: Install stringer again (idempotent)
community.general.golang_package:
name: golang.org/x/tools/cmd/stringer
version: v0.24.1
register: install_idem
- name: Uninstall stringer
community.general.golang_package:
state: absent
name: golang.org/x/tools/cmd/stringer
register: uninstall_result
- name: Uninstall stringer again (idempotent)
community.general.golang_package:
state: absent
name: golang.org/x/tools/cmd/stringer
register: uninstall_idem
- name: Check assertions
assert:
that:
- uninstall_absent is not changed
- install_result is changed
- install_idem is not changed
- uninstall_result is changed
- uninstall_idem is not changed

View file

@ -0,0 +1,47 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# x/tools v0.24.1 is the only version that works on both Go 1.16-1.24 AND Go 1.25+.
# See https://github.com/golang/go/issues/74462 for details.
- name: Install stringer at specific version (version param)
community.general.golang_package:
name: golang.org/x/tools/cmd/stringer
version: v0.24.1
register: install_version
- name: Install stringer at same version (idempotent)
community.general.golang_package:
name: golang.org/x/tools/cmd/stringer
version: v0.24.1
register: install_version_idem
- name: Clean up stringer
community.general.golang_package:
name: golang.org/x/tools/cmd/stringer
state: absent
- name: Install stringer at specific version (inline @version)
community.general.golang_package:
name: golang.org/x/tools/cmd/stringer@v0.24.1
register: install_inline_version
- name: Install stringer at same inline version (idempotent)
community.general.golang_package:
name: golang.org/x/tools/cmd/stringer@v0.24.1
register: install_inline_version_idem
- name: Clean up stringer
community.general.golang_package:
name: golang.org/x/tools/cmd/stringer
state: absent
- name: Check assertions
assert:
that:
- install_version is changed
- install_version_idem is not changed
- install_inline_version is changed
- install_inline_version_idem is not changed