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

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/go-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Run Go Tests

on:
push:
branches: ["**"]
pull_request:
branches: ["**"]

jobs:
go-test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.24.0

- name: Install Task
run: |
sh -s -- -d -b /usr/local/bin <<EOF
$(curl -s https://taskfile.dev/install.sh)
EOF

- name: Get dependencies
run: go mod tidy

- name: Run tests
run: task test-linux
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.bin/
43 changes: 43 additions & 0 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

version: "3"
vars:
GOLANGCI_LINT_VERSION: v2.1.6
GOIMPORTS_VERSION: v0.34.0
DPRINT_VERSION: 0.50.0
tasks:
init:
desc: Setup local env
deps:
- install:linter
- install:goimports

install:linter:
cmds:
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b .bin/ {{ .GOLANGCI_LINT_VERSION }}

install:goimports:
cmds:
- go install golang.org/x/tools/cmd/goimports@{{ .GOIMPORTS_VERSION }}

test:
desc: Run all tests
cmds:
- go test ./... -v -race {{ .CLI_ARGS }}

lint:
desc: Run the linters
cmds:
- ${PWD}/.bin/golangci-lint run --fix -v --timeout 300s {{ .CLI_ARGS }}

fmt:
desc: Run format
cmds:
- goimports -l -w .

test-linux:
desc: Build test Linux test image
cmds:
- docker build -f testdata/Dockerfile -t go-apt-test:latest .
- docker run --rm go-apt-test


8 changes: 4 additions & 4 deletions apt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ package apt
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"testing"

"github.com/stretchr/testify/require"
)

func TestList(t *testing.T) {
out, err := ioutil.ReadFile("testdata/dpkg-query-output-1.txt")
out, err := os.ReadFile("testdata/dpkg-query-output-1.txt")
require.NoError(t, err, "Reading test input data")
list := parseDpkgQueryOutput(out)

// Check list with expected output
data, err := ioutil.ReadFile("testdata/dpkg-query-output-1-result.json")
data, err := os.ReadFile("testdata/dpkg-query-output-1-result.json")
require.NoError(t, err, "Reading test result data")
var expected []*Package
err = json.Unmarshal(data, &expected)
Expand All @@ -49,7 +49,7 @@ func TestSearch(t *testing.T) {
require.NoError(t, err, "running Search command")
require.Empty(t, list, "Search command result")

list, err = Search("header")
list, err = Search("bash") // "bash" is almost always present on Linux systems
require.NoError(t, err, "running Search command")
require.NotEmpty(t, list, "Search command result")
}
Expand Down
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/arduino/go-apt-client

go 1.24.0

require (
github.com/google/go-cmp v0.7.0
github.com/stretchr/testify v1.10.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
52 changes: 27 additions & 25 deletions repos.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -127,9 +126,9 @@ func parseAPTConfigLine(line string) *Repository {
}

func parseAPTConfigFile(configPath string) (RepositoryList, error) {
data, err := ioutil.ReadFile(configPath)
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("Reading %s: %s", configPath, err)
return nil, fmt.Errorf("reading %s: %s", configPath, err)
}
scanner := bufio.NewScanner(bytes.NewReader(data))

Expand All @@ -153,9 +152,9 @@ func ParseAPTConfigFolder(folderPath string) (RepositoryList, error) {
sources := []string{filepath.Join(folderPath, "sources.list")}

sourcesFolder := filepath.Join(folderPath, "sources.list.d")
list, err := ioutil.ReadDir(sourcesFolder)
list, err := os.ReadDir(sourcesFolder)
if err != nil {
return nil, fmt.Errorf("Reading %s folder: %s", sourcesFolder, err)
return nil, fmt.Errorf("reading %s folder: %s", sourcesFolder, err)
}
for _, l := range list {
if strings.HasSuffix(l.Name(), ".list") {
Expand All @@ -167,7 +166,7 @@ func ParseAPTConfigFolder(folderPath string) (RepositoryList, error) {
for _, source := range sources {
repos, err := parseAPTConfigFile(source)
if err != nil {
return nil, fmt.Errorf("Parsing %s: %s", source, err)
return nil, fmt.Errorf("parsing %s: %s", source, err)
}
res = append(res, repos...)
}
Expand All @@ -183,7 +182,7 @@ func AddRepository(repo *Repository, configFolderPath string) error {
return fmt.Errorf("parsing APT config: %s", err)
}
if repos.Contains(repo) {
return fmt.Errorf("The repository is already configured")
return fmt.Errorf("the repository is already configured")
}

// Add to the "managed.list" file
Expand All @@ -193,11 +192,11 @@ func AddRepository(repo *Repository, configFolderPath string) error {
f, err = os.OpenFile(managedPath, os.O_CREATE|os.O_WRONLY, 0644)
}
if err != nil {
return fmt.Errorf("Opening %s: %s", managedPath, err)
return fmt.Errorf("opening %s: %s", managedPath, err)
}
defer f.Close()
defer f.Close() //nolint:errcheck
if _, err = f.WriteString(repo.APTConfigLine() + "\n"); err != nil {
return fmt.Errorf("Writing repo data to config file %s: %s", managedPath, err)
return fmt.Errorf("writing repo data to config file %s: %s", managedPath, err)
}
return nil
}
Expand All @@ -214,14 +213,14 @@ func RemoveRepository(repo *Repository, configFolderPath string) error {
// Find the repo to remove
repoToRemove := repos.Find(repo)
if repoToRemove == nil {
return fmt.Errorf("Repository already removed")
return fmt.Errorf("repository already removed")
}

// Read the config file that contains the repo config to remove
fileToFilter := repoToRemove.configFile
data, err := ioutil.ReadFile(fileToFilter)
data, err := os.ReadFile(fileToFilter)
if err != nil {
return fmt.Errorf("Reading config file %s: %s", fileToFilter, err)
return fmt.Errorf("reading config file %s: %s", fileToFilter, err)
}

// Create the new version of the file
Expand All @@ -230,7 +229,7 @@ func RemoveRepository(repo *Repository, configFolderPath string) error {
for scanner.Scan() {
line := scanner.Text()
r := parseAPTConfigLine(line)
if r!= nil && r.Equals(repo) {
if r != nil && r.Equals(repo) {
// Filter repo configs that match the repo to be removed
continue
}
Expand All @@ -239,7 +238,7 @@ func RemoveRepository(repo *Repository, configFolderPath string) error {

err = replaceFile(fileToFilter, []byte(newContent))
if err != nil {
return fmt.Errorf("Writing of new config: %s", err)
return fmt.Errorf("writing of new config: %s", err)
}

return nil
Expand All @@ -257,14 +256,14 @@ func EditRepository(old *Repository, new *Repository, configFolderPath string) e
// Find the repo to edit
repoToEdit := repos.Find(old)
if repoToEdit == nil {
return fmt.Errorf("Repository doesn't exist")
return fmt.Errorf("repository doesn't exist")
}

// Read the config file that contains the repo configuration to edit
fileToEdit := repoToEdit.configFile
data, err := ioutil.ReadFile(fileToEdit)
data, err := os.ReadFile(fileToEdit)
if err != nil {
return fmt.Errorf("Reading config file %s: %s", fileToEdit, err)
return fmt.Errorf("reading config file %s: %s", fileToEdit, err)
}

// Create the new version of the file
Expand All @@ -283,7 +282,7 @@ func EditRepository(old *Repository, new *Repository, configFolderPath string) e

err = replaceFile(fileToEdit, []byte(newContent))
if err != nil {
return fmt.Errorf("Writing of new config: %s", err)
return fmt.Errorf("writing of new config: %s", err)
}

return nil
Expand All @@ -294,26 +293,29 @@ func replaceFile(path string, newContent []byte) error {
backupPath := path + ".save"

// Create the new version of the file
err := ioutil.WriteFile(newPath, newContent, 0644)
err := os.WriteFile(newPath, newContent, 0644)
if err != nil {
return fmt.Errorf("Creating replacement file for %s: %s", newPath, err)
return fmt.Errorf("creating replacement file for %s: %s", newPath, err)
}

// Only in case of error clean-up the new copy (otherwise ignore the error...)
defer os.Remove(newPath)
defer os.Remove(newPath) //nolint:errcheck

// Make a backup copy
err = os.Rename(path, backupPath)
if err != nil {
return fmt.Errorf("Making backup copy of %s: %s", path, err)
return fmt.Errorf("making backup copy of %s: %s", path, err)
}

// Rename the new copy to the final path
err = os.Rename(newPath, path)
if err != nil {
// Something went wrong... try to rollback the backup
os.Rename(backupPath, path)
return fmt.Errorf("Renaming %s to %s: %s", newPath, path, err)
err := os.Rename(backupPath, path)
if err != nil {
return fmt.Errorf("rolling back backup copy of %s: %s", path, err)
}
return fmt.Errorf("renaming %s to %s: %s", newPath, path, err)
}

return nil
Expand Down
15 changes: 9 additions & 6 deletions repos_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,36 @@ package apt

import (
"encoding/json"
"io/ioutil"
"os"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"

"github.com/stretchr/testify/require"
)

func TestParseAPTConfigFolder(t *testing.T) {
repos, err := ParseAPTConfigFolder("testdata/apt")
require.NoError(t, err, "running List command")

expectedData, err := ioutil.ReadFile("testdata/TestParseAPTConfigFolder.json")
expectedData, err := os.ReadFile("testdata/TestParseAPTConfigFolder.json")
require.NoError(t, err, "Reading test data")
expected := []*Repository{}
err = json.Unmarshal(expectedData, &expected)
require.NoError(t, err, "Decoding expected data")

for i, repo := range repos {
require.EqualValues(t, expected[i], repo, "Comparing element %d", i)
assert.Empty(t, cmp.Diff(expected[i], repo, cmpopts.IgnoreFields(Repository{}, "configFile")))
}
}

func TestAddAndRemoveRepository(t *testing.T) {
// test cleanup
defer os.Remove("testdata/apt2/sources.list.d/managed.list")
defer os.Remove("testdata/apt2/sources.list.d/managed.list.save")
defer os.Remove("testdata/apt2/sources.list.d/managed.list.new")
defer os.Remove("testdata/apt2/sources.list.d/managed.list") //nolint:errcheck
defer os.Remove("testdata/apt2/sources.list.d/managed.list.save") //nolint:errcheck
defer os.Remove("testdata/apt2/sources.list.d/managed.list.new") //nolint:errcheck

repo1 := &Repository{
Enabled: true,
Expand Down
19 changes: 19 additions & 0 deletions testdata/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM golang:1.24

# Install dpkg/apt tools
RUN apt-get update && apt-get install -y \
dpkg \
apt \
&& rm -rf /var/lib/apt/lists/*

# Set working directory inside container
WORKDIR /app

# Copy everything (mod files + code)
COPY . .

# Download modules
RUN go mod download

# Run tests in apt package
CMD ["go", "test", "-v", "./"]
Empty file.