mirror of
https://github.com/awalsh128/cache-apt-pkgs-action.git
synced 2025-09-20 18:17:15 +00:00
Go based package query logic.
This commit is contained in:
parent
36fa5367f6
commit
a366c76c51
21
.github/workflows/pull_request.yml
vendored
Normal file
21
.github/workflows/pull_request.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
name: Pull Request
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: 1.20
|
||||||
|
|
||||||
|
- name: Build and test
|
||||||
|
run: |
|
||||||
|
go build -v
|
||||||
|
go test -v ./...
|
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Package",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${fileDirname}",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
106
apt_query.go
Normal file
106
apt_query.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func contains(arr []string, element string) bool {
|
||||||
|
for _, x := range arr {
|
||||||
|
if x == element {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes a message to STDERR and exits with status 1.
|
||||||
|
func exitOnError(format string, arg ...any) {
|
||||||
|
fmt.Fprintln(os.Stderr, fmt.Errorf(format+"\n", arg...))
|
||||||
|
fmt.Println("Usage: apt_query.go normalized-list <package names>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AptPackage struct {
|
||||||
|
Name string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes a command and either returns the output or exits the programs and writes the output (including error) to STDERR.
|
||||||
|
func execCommand(name string, arg ...string) string {
|
||||||
|
cmd := exec.Command(name, arg...)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, fmt.Sprintf("Error code %d encountered while running %s\n%s", cmd.ProcessState.ExitCode(), strings.Join(cmd.Args, " "), string(out)))
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the APT based packages as a sorted by name list (normalized).
|
||||||
|
func getPackages(names []string) []AptPackage {
|
||||||
|
prefixArgs := []string{"--quiet=0", "--no-all-versions", "show"}
|
||||||
|
out := execCommand("apt-cache", append(prefixArgs, names...)...)
|
||||||
|
|
||||||
|
packages := []AptPackage{}
|
||||||
|
errorMessages := []string{}
|
||||||
|
|
||||||
|
for _, paragraph := range strings.Split(string(out), "\n\n") {
|
||||||
|
pkg := AptPackage{}
|
||||||
|
for _, line := range strings.Split(paragraph, "\n") {
|
||||||
|
if strings.HasPrefix(line, "Package: ") {
|
||||||
|
pkg.Name = strings.TrimSpace(strings.Split(line, ":")[1])
|
||||||
|
} else if strings.HasPrefix(line, "Version: ") {
|
||||||
|
pkg.Version = strings.TrimSpace(strings.Split(line, ":")[1])
|
||||||
|
} else if strings.HasPrefix(line, "N: ") || strings.HasPrefix(line, "E: ") {
|
||||||
|
if !contains(errorMessages, line) {
|
||||||
|
errorMessages = append(errorMessages, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pkg.Name != "" {
|
||||||
|
packages = append(packages, pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errorMessages) > 0 {
|
||||||
|
exitOnError("Errors encountered in apt-cache output (see below):\n%s", strings.Join(errorMessages, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(packages, func(i, j int) bool {
|
||||||
|
return packages[i].Name < packages[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
return packages
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialize(packages []AptPackage) string {
|
||||||
|
tokens := []string{}
|
||||||
|
for _, pkg := range packages {
|
||||||
|
tokens = append(tokens, pkg.Name+"="+pkg.Version)
|
||||||
|
}
|
||||||
|
return strings.Join(tokens, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
const usageText string = "Usage: apt_query.go [normalized-list] <package names>"
|
||||||
|
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
exitOnError("Expected at least 2 arguments but found %d.", len(os.Args)-1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
command := os.Args[1]
|
||||||
|
packageNames := os.Args[2:]
|
||||||
|
|
||||||
|
switch command {
|
||||||
|
case "normalized-list":
|
||||||
|
fmt.Println(serialize(getPackages(packageNames)))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
exitOnError("Command '%s' not recognized.", command)
|
||||||
|
}
|
||||||
|
}
|
79
apt_query_test.go
Normal file
79
apt_query_test.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RunResult struct {
|
||||||
|
TestContext *testing.T
|
||||||
|
Stdout string
|
||||||
|
Stderr string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(t *testing.T, command string, pkgNames ...string) RunResult {
|
||||||
|
stdout := &bytes.Buffer{}
|
||||||
|
stderr := &bytes.Buffer{}
|
||||||
|
cmd := exec.Command("go", append([]string{"run", "apt_query.go", command}, pkgNames...)...)
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
return RunResult{TestContext: t, Stdout: stdout.String(), Stderr: stderr.String(), Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RunResult) expectSuccessfulOut(expected string) {
|
||||||
|
if r.Err != nil {
|
||||||
|
r.TestContext.Errorf("Error running command: %v", r.Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Stdout != expected+"\n" { // Output will always have a end of output newline.
|
||||||
|
r.TestContext.Errorf("Unexpected stdout found.\nExpected:\n'%s'\nActual:\n'%s'", expected, r.Stdout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RunResult) expectError(expected string) {
|
||||||
|
if r.Stderr != expected+"\n" { // Output will always have a end of output newline.
|
||||||
|
r.TestContext.Errorf("Unexpected stderr found.\nExpected:\n'%s'\nActual:\n'%s'", expected, r.Stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizedList_MultiplePackagesExists_StdoutsAlphaSortedPackageNameVersionPairs(t *testing.T) {
|
||||||
|
result := run(t, "normalized-list", "xdot", "rolldice")
|
||||||
|
result.expectSuccessfulOut("rolldice=1.16-1build1 xdot=1.2-3")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizedList_SamePackagesDifferentOrder_StdoutsMatch(t *testing.T) {
|
||||||
|
expected := "rolldice=1.16-1build1 xdot=1.2-3"
|
||||||
|
|
||||||
|
result := run(t, "normalized-list", "rolldice", "xdot")
|
||||||
|
result.expectSuccessfulOut(expected)
|
||||||
|
|
||||||
|
result = run(t, "normalized-list", "xdot", "rolldice")
|
||||||
|
result.expectSuccessfulOut(expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizedList_SinglePackageExists_StdoutsSinglePackageNameVersionPair(t *testing.T) {
|
||||||
|
var result = run(t, "normalized-list", "xdot")
|
||||||
|
result.expectSuccessfulOut("xdot=1.2-3")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizedList_NonExistentPackageName_StderrsAptCacheErrors(t *testing.T) {
|
||||||
|
var result = run(t, "normalized-list", "nonexistentpackagename")
|
||||||
|
result.expectError(
|
||||||
|
`Error code 100 encountered while running apt-cache --quiet=0 --no-all-versions show nonexistentpackagename
|
||||||
|
N: Unable to locate package nonexistentpackagename
|
||||||
|
N: Unable to locate package nonexistentpackagename
|
||||||
|
E: No packages found
|
||||||
|
|
||||||
|
exit status 2`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizedList_NoPackagesGiven_StderrsArgMismatch(t *testing.T) {
|
||||||
|
var result = run(t, "normalized-list")
|
||||||
|
result.expectError(
|
||||||
|
`Expected at least 2 arguments but found 1.
|
||||||
|
|
||||||
|
exit status 1`)
|
||||||
|
}
|
Loading…
Reference in a new issue