├── .gitignore
├── changes
├── .keep
└── README.md
├── utils
├── filesystem
│ ├── testdata
│ │ ├── embed
│ │ │ ├── level1
│ │ │ │ └── test.txt
│ │ │ └── test.txt
│ │ ├── invalidzipfile.zip
│ │ ├── t.zip
│ │ ├── 10.zip
│ │ ├── 1KB.bin
│ │ ├── 1KB.gz
│ │ ├── 42.zip
│ │ ├── 5MB.zip
│ │ ├── child.zip
│ │ ├── k64f.pack
│ │ ├── zbsm.zip
│ │ ├── zip-bomb.zip
│ │ ├── testuntar.tar
│ │ ├── testunzip.zip
│ │ ├── testunzip2.7z
│ │ ├── testunzip2.zip
│ │ ├── testunzip3.zip
│ │ ├── zipwithnonutf8.zip
│ │ ├── validlargezipfile.zip
│ │ ├── zip-bomb-nested-large.zip
│ │ ├── zip-bomb-nested-small.zip
│ │ ├── abovefilecountlimitzip.zip
│ │ ├── belowfilecountlimitzip.zip
│ │ └── zipwithnonutf8filenames2.zip
│ ├── embedfs.go
│ ├── limits_test.go
│ ├── files_windows.go
│ ├── iofs_test.go
│ ├── files_posix.go
│ ├── filesystem_test.go
│ ├── iofs.go
│ ├── tarfs.go
│ ├── zipfs.go
│ ├── tar.go
│ ├── filetimes.go
│ ├── extendedosfs.go
│ ├── files_unix_test.go
│ └── filehash.go
├── .gitignore
├── semver
│ ├── testdata
│ │ ├── invalid2.properties
│ │ ├── module.properties
│ │ └── invalid.properties
│ ├── sanitisation.go
│ ├── semver.go
│ └── sanitisation_test.go
├── subprocess
│ ├── testdata
│ │ └── echo_stdout_and_stderr.sh
│ ├── command_wrapper_darwin.go
│ ├── command_wrapper_linux.go
│ ├── io_test.go
│ ├── command_wrapper_windows.go
│ ├── supervisor
│ │ └── interface.go
│ ├── logging.go
│ ├── command
│ │ └── cmd_test.go
│ └── io.go
├── diodes
│ ├── README
│ ├── waiter.go
│ └── poller.go
├── module.properties
├── config
│ ├── fixtures
│ │ ├── flat-config-test.json
│ │ ├── nested-config-test.json
│ │ ├── config-test.json
│ │ └── env-test.env
│ ├── interfaces.go
│ ├── validation_test.go
│ └── validation.go
├── logs
│ ├── logrimp
│ │ ├── noop.go
│ │ ├── zap.go
│ │ ├── hclog.go
│ │ ├── logrus.go
│ │ ├── slog.go
│ │ ├── stdout.go
│ │ ├── quiet_logger.go
│ │ ├── quiet_logger_test.go
│ │ └── logr_test.go
│ ├── noop_logger.go
│ ├── pipe_logger_test.go
│ ├── noop_logger_test.go
│ ├── logrus_logger_test.go
│ ├── quiet_logger_test.go
│ ├── slog_logger_test.go
│ ├── pipe_logger.go
│ ├── std_logger_test.go
│ ├── slog_logger.go
│ ├── zap_logger_test.go
│ ├── logstest
│ │ ├── testing.go
│ │ └── testing_test.go
│ ├── quiet_logger.go
│ ├── hclog_logger_test.go
│ ├── string_logger_test.go
│ ├── writer_test.go
│ ├── zap_logger.go
│ ├── logrus_logger.go
│ ├── hclog_logger.go
│ ├── json_logger_test.go
│ ├── log_test.go
│ └── interfaces.go
├── serialization
│ ├── testdata
│ │ └── testfile_GB2312.xml
│ ├── xml.go
│ ├── slice
│ │ └── slice.go
│ ├── maps
│ │ └── testing
│ │ │ └── testing.go
│ └── xml_test.go
├── charset
│ ├── utf8.go
│ ├── unsupportedcharset_index.go
│ └── iconv
│ │ ├── interfaces.go
│ │ └── converter.go
├── platform
│ ├── homedir_darwin.go
│ ├── os_windows.go
│ ├── os_posix.go
│ ├── homedir_windows.go
│ ├── homedir_unix.go
│ ├── deletion_posix.go
│ ├── deletion_windows.go
│ ├── deletion_test.go
│ ├── homedir_test.go
│ ├── cmd_windows.go
│ ├── cmd.go
│ ├── cmd_test.go
│ ├── cmd_posix.go
│ ├── homedir.go
│ ├── users_windows.go
│ └── users_windows_arm64.go
├── git
│ ├── config_test.go
│ ├── limits_test.go
│ └── interfaces.go
├── maps
│ ├── ReadME.md
│ ├── overall_test.go
│ └── map.go
├── units
│ ├── multiplication
│ │ ├── units_test.go
│ │ └── units.go
│ └── size
│ │ └── sizetest
│ │ └── units_test.go
├── sharedcache
│ ├── config_test.go
│ ├── factory.go
│ ├── config.go
│ └── interface.go
├── http
│ ├── headers
│ │ ├── interfaces.go
│ │ └── useragent
│ │ │ ├── useragent_test.go
│ │ │ ├── useragent.go
│ │ │ └── useragent
│ │ │ └── useragent.go
│ ├── common.go
│ ├── retry.go
│ ├── retry_configuration_test.go
│ ├── httptest
│ │ └── testing.go
│ ├── configuration_test.go
│ ├── pooled_client.go
│ ├── retry_configuration.go
│ ├── request_test.go
│ └── schemes
│ │ └── schemes.go
├── safeio
│ ├── error_test.go
│ ├── error.go
│ ├── write_test.go
│ ├── write.go
│ └── read_closer_test.go
├── encryption
│ ├── interface.go
│ └── aesrsa
│ │ └── testhelpers
│ │ └── helper.go
├── idgen
│ ├── uuid.go
│ └── uuid_test.go
├── resource
│ ├── interfaces.go
│ ├── resource_test.go
│ └── resource.go
├── hashing
│ └── interfaces.go
├── commonerrors
│ └── errortest
│ │ ├── testing_test.go
│ │ └── testing.go
├── safecast
│ ├── ReadMe.md
│ ├── boundary.go
│ └── number.go
├── strings
│ ├── strings.go
│ └── strings_test.go
├── proc
│ ├── find
│ │ ├── find.go
│ │ ├── find_other.go
│ │ └── find_test.go
│ ├── ps_windows.go
│ ├── ps_posix.go
│ └── errors.go
├── signing
│ └── interface.go
├── cache
│ └── filecache
│ │ ├── config.go
│ │ ├── entry.go
│ │ ├── lockmap.go
│ │ └── entrymap.go
├── collection
│ ├── modify.go
│ ├── range.go
│ ├── range_test.go
│ └── modify_test.go
├── retry
│ ├── retry_configuration_test.go
│ └── retry.go
├── value
│ └── empty.go
├── parallelisation
│ ├── wait.go
│ ├── mocks
│ │ └── mock_parallelisation.go
│ └── cancel_functions.go
├── transaction
│ └── saga
│ │ └── types.go
├── mocks
│ ├── mock_supervisor.go
│ └── mock_subprocess.go
├── keyring
│ └── keyring_test.go
└── validation
│ └── rules.go
├── .github
├── CODEOWNERS
├── dependabot.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── PULL_REQUEST_TEMPLATE.md
├── ISSUE_TEMPLATE.md
└── workflows
│ ├── release.yml
│ └── dependabot.yml
├── SECURITY.md
├── KNOWN_ISSUES.md
├── .pre-commit-config.yaml
├── .golangci.pre-commit.yaml
├── .goreleaser.yml
├── .codeclimate.yml
└── .golangci.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | dist/
3 | .idea
4 |
--------------------------------------------------------------------------------
/changes/.keep:
--------------------------------------------------------------------------------
1 | Keeping this folder present
2 |
--------------------------------------------------------------------------------
/utils/filesystem/testdata/embed/level1/test.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/filesystem/testdata/invalidzipfile.zip:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | .idea
3 |
--------------------------------------------------------------------------------
/utils/semver/testdata/invalid2.properties:
--------------------------------------------------------------------------------
1 | versionIncorrect=3.0.1
--------------------------------------------------------------------------------
/utils/filesystem/testdata/embed/test.txt:
--------------------------------------------------------------------------------
1 | this is a text file with some content
2 |
--------------------------------------------------------------------------------
/utils/subprocess/testdata/echo_stdout_and_stderr.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "$1" | tee /dev/stderr
4 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Default owners for everything in the repo.
2 | * @ARM-software/golang-utils-admin
3 |
--------------------------------------------------------------------------------
/utils/filesystem/testdata/t.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/t.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/10.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/10.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/1KB.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/1KB.bin
--------------------------------------------------------------------------------
/utils/filesystem/testdata/1KB.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/1KB.gz
--------------------------------------------------------------------------------
/utils/filesystem/testdata/42.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/42.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/5MB.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/5MB.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/child.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/child.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/k64f.pack:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/k64f.pack
--------------------------------------------------------------------------------
/utils/filesystem/testdata/zbsm.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/zbsm.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/zip-bomb.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/zip-bomb.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/testuntar.tar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/testuntar.tar
--------------------------------------------------------------------------------
/utils/filesystem/testdata/testunzip.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/testunzip.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/testunzip2.7z:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/testunzip2.7z
--------------------------------------------------------------------------------
/utils/filesystem/testdata/testunzip2.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/testunzip2.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/testunzip3.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/testunzip3.zip
--------------------------------------------------------------------------------
/utils/semver/testdata/module.properties:
--------------------------------------------------------------------------------
1 | Version=1.0.1-test
2 | MajorVersion=1
3 | MinorVersion=0
4 | PatchVersion=1
5 | CommitHash=123456789
--------------------------------------------------------------------------------
/utils/semver/testdata/invalid.properties:
--------------------------------------------------------------------------------
1 | Version=this is invalid
2 | MajorVersion=1
3 | MinorVersion=0
4 | PatchVersion=1
5 | CommitHash=123456789
--------------------------------------------------------------------------------
/utils/diodes/README:
--------------------------------------------------------------------------------
1 | Vendoring [cloud foundary](https://github.com/cloudfoundry/go-diodes)) diode libraries to avoid importing test dependencies.
2 |
3 |
--------------------------------------------------------------------------------
/utils/filesystem/testdata/zipwithnonutf8.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/zipwithnonutf8.zip
--------------------------------------------------------------------------------
/utils/module.properties:
--------------------------------------------------------------------------------
1 | Version=1.140.0
2 | MajorVersion=1
3 | MinorVersion=140
4 | PatchVersion=0
5 | CommitHash=39f2252e62d7ddbcb9237cbab5200d61b2a26c39
--------------------------------------------------------------------------------
/utils/config/fixtures/flat-config-test.json:
--------------------------------------------------------------------------------
1 | {
2 | "embedded1": "embedded 1",
3 | "embedded2": "embedded 2",
4 | "non_embedded1": "non-embedded 1"
5 | }
--------------------------------------------------------------------------------
/utils/filesystem/testdata/validlargezipfile.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/validlargezipfile.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/zip-bomb-nested-large.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/zip-bomb-nested-large.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/zip-bomb-nested-small.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/zip-bomb-nested-small.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/abovefilecountlimitzip.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/abovefilecountlimitzip.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/belowfilecountlimitzip.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/belowfilecountlimitzip.zip
--------------------------------------------------------------------------------
/utils/filesystem/testdata/zipwithnonutf8filenames2.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARM-software/golang-utils/HEAD/utils/filesystem/testdata/zipwithnonutf8filenames2.zip
--------------------------------------------------------------------------------
/utils/logs/logrimp/noop.go:
--------------------------------------------------------------------------------
1 | package logrimp
2 |
3 | import "github.com/go-logr/logr"
4 |
5 | // NewNoopLogger returns a discarding.
6 | func NewNoopLogger() logr.Logger {
7 | return logr.Discard()
8 | }
9 |
--------------------------------------------------------------------------------
/utils/serialization/testdata/testfile_GB2312.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | http://www.keil.com/pack/
4 | 世界
5 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | If finding a security vulnerability in this library, please report it to us using the following [guidelines](https://developer.arm.com/documentation/102850/latest/).
6 |
--------------------------------------------------------------------------------
/utils/charset/utf8.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package charset
6 |
7 | const InvalidUTF8CharacterReplacement = "?"
8 |
--------------------------------------------------------------------------------
/utils/platform/homedir_darwin.go:
--------------------------------------------------------------------------------
1 | //go:build darwin
2 |
3 | package platform
4 |
5 | import "fmt"
6 |
7 | func determineDefaultHomeDirectory(username string) (string, error) {
8 | return fmt.Sprintf("/Users/%v", username), nil
9 | }
10 |
--------------------------------------------------------------------------------
/utils/git/config_test.go:
--------------------------------------------------------------------------------
1 | package git
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestNewConfig(t *testing.T) {
10 | require.NoError(t, DefaultLimits().Validate()) //nolint:typecheck
11 | }
12 |
--------------------------------------------------------------------------------
/utils/maps/ReadME.md:
--------------------------------------------------------------------------------
1 | This module was initially created to vendor https://pkg.go.dev/github.com/hashicorp/terraform/flatmap which has been
2 | removed from the terraform project.
3 |
4 | code has been updated and is also inspired from https://github.com/astaxie/flatmap
--------------------------------------------------------------------------------
/KNOWN_ISSUES.md:
--------------------------------------------------------------------------------
1 |
5 | # Known Issues
6 |
7 | This project is a work-in-progress and we are not currently tracking issues.
8 |
--------------------------------------------------------------------------------
/utils/units/multiplication/units_test.go:
--------------------------------------------------------------------------------
1 | package multiplication
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestNumbers(t *testing.T) {
10 | assert.Equal(t, Kilo, float64(1000))
11 | assert.Equal(t, Mega, float64(1000000))
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/utils/config/fixtures/nested-config-test.json:
--------------------------------------------------------------------------------
1 | {
2 | "testbasecfg": {
3 | "embedded1": "embedded 1",
4 | "embedded2": "embedded 2"
5 | },
6 | "embedded_struct": {
7 | "embedded1": "embedded 1",
8 | "embedded2": "embedded 2"
9 | },
10 | "non_embedded1": "non-embedded 1"
11 | }
--------------------------------------------------------------------------------
/utils/logs/logrimp/zap.go:
--------------------------------------------------------------------------------
1 | package logrimp
2 |
3 | import (
4 | "github.com/go-logr/logr"
5 | "github.com/go-logr/zapr"
6 | "go.uber.org/zap"
7 | )
8 |
9 | // NewZapLogger returns a new zap logger
10 | func NewZapLogger(logger *zap.Logger) logr.Logger {
11 | return zapr.NewLogger(logger)
12 | }
13 |
--------------------------------------------------------------------------------
/utils/platform/os_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package platform
4 |
5 | import "golang.org/x/sys/windows/registry"
6 |
7 | func expandFromEnvironment(s string) string {
8 | expanded, err := registry.ExpandString(s)
9 | if err == nil {
10 | return expanded
11 | }
12 | return s
13 | }
14 |
--------------------------------------------------------------------------------
/utils/platform/os_posix.go:
--------------------------------------------------------------------------------
1 | //go:build linux || unix || (js && wasm) || darwin || aix || dragonfly || freebsd || nacl || netbsd || openbsd || solaris
2 |
3 | package platform
4 |
5 | func expandFromEnvironment(s string) string {
6 | // nothing to do on unix system as it should be covered by os.ExpandEnv
7 | return s
8 | }
9 |
--------------------------------------------------------------------------------
/utils/logs/logrimp/hclog.go:
--------------------------------------------------------------------------------
1 | package logrimp
2 |
3 | import (
4 | "github.com/evanphx/hclogr"
5 | "github.com/go-logr/logr"
6 | "github.com/hashicorp/go-hclog"
7 | )
8 |
9 | // NewHclogLogger returns a new HCLog logger.
10 | func NewHclogLogger(logger hclog.Logger) logr.Logger {
11 | return hclogr.Wrap(logger)
12 | }
13 |
--------------------------------------------------------------------------------
/utils/logs/logrimp/logrus.go:
--------------------------------------------------------------------------------
1 | package logrimp
2 |
3 | import (
4 | "github.com/bombsimon/logrusr/v4"
5 | "github.com/go-logr/logr"
6 | "github.com/sirupsen/logrus"
7 | )
8 |
9 | // NewLogrusLogger returns a logrus logger.
10 | func NewLogrusLogger(logger logrus.FieldLogger, opts ...logrusr.Option) logr.Logger {
11 | return logrusr.New(logger, opts...)
12 | }
13 |
--------------------------------------------------------------------------------
/utils/subprocess/command_wrapper_darwin.go:
--------------------------------------------------------------------------------
1 | //go:build darwin
2 |
3 | package subprocess
4 |
5 | import (
6 | "os/exec"
7 | "syscall"
8 | )
9 |
10 | // See https://github.com/tgulacsi/go/blob/master/proc/
11 | func setGroupAttrToCmd(c *exec.Cmd) {
12 | c.SysProcAttr = &syscall.SysProcAttr{
13 | Setpgid: true, // to be able to kill all children, too
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/utils/subprocess/command_wrapper_linux.go:
--------------------------------------------------------------------------------
1 | //go:build linux
2 |
3 | package subprocess
4 |
5 | import (
6 | "os/exec"
7 | "syscall"
8 | )
9 |
10 | // See https://github.com/tgulacsi/go/blob/master/proc/
11 | func setGroupAttrToCmd(c *exec.Cmd) {
12 | c.SysProcAttr = &syscall.SysProcAttr{
13 | Setpgid: true, // to be able to kill all children, too
14 | Pdeathsig: syscall.SIGKILL,
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/utils/subprocess/io_test.go:
--------------------------------------------------------------------------------
1 | package subprocess
2 |
3 | import (
4 | "context"
5 | "os"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestDefaultIO(t *testing.T) {
12 | io := NewDefaultIO()
13 | in, out, errs := io.Register(context.Background())
14 | assert.Equal(t, os.Stdin, in)
15 | assert.Equal(t, os.Stdout, out)
16 | assert.Equal(t, os.Stderr, errs)
17 | }
18 |
--------------------------------------------------------------------------------
/utils/sharedcache/config_test.go:
--------------------------------------------------------------------------------
1 | package sharedcache
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/go-faker/faker/v4"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestDefaultSharedCacheConfiguration(t *testing.T) {
11 | cfg := DefaultSharedCacheConfiguration()
12 | require.Error(t, cfg.Validate())
13 | cfg.RemoteStoragePath = faker.URL()
14 | require.NoError(t, cfg.Validate())
15 | }
16 |
--------------------------------------------------------------------------------
/utils/logs/noop_logger.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "github.com/ARM-software/golang-utils/utils/logs/logrimp"
9 | )
10 |
11 | func NewNoopLogger(loggerSource string) (loggers Loggers, err error) {
12 | return NewLogrLogger(logrimp.NewNoopLogger(), loggerSource)
13 | }
14 |
--------------------------------------------------------------------------------
/utils/filesystem/embedfs.go:
--------------------------------------------------------------------------------
1 | package filesystem
2 |
3 | import (
4 | "embed"
5 |
6 | "github.com/spf13/afero"
7 |
8 | "github.com/ARM-software/golang-utils/utils/commonerrors"
9 | )
10 |
11 | func newEmbedFSAdapter(fs *embed.FS) (afero.Fs, error) {
12 | if fs == nil {
13 | return nil, commonerrors.UndefinedVariable("embedded file system")
14 | }
15 | return afero.NewReadOnlyFs(afero.FromIOFS{
16 | FS: *fs,
17 | }), nil
18 | }
19 |
--------------------------------------------------------------------------------
/utils/logs/logrimp/slog.go:
--------------------------------------------------------------------------------
1 | package logrimp
2 |
3 | import (
4 | "log/slog"
5 |
6 | "github.com/go-logr/logr"
7 | )
8 |
9 | // NewSlogLogger returns a new [slog logger](see https://pkg.go.dev/golang.org/x/exp/slog) which will be part of the standard library.
10 | func NewSlogLogger(logger *slog.Logger) logr.Logger {
11 | if logger == nil {
12 | return logr.Discard()
13 | }
14 | return logr.FromSlogHandler(logger.Handler())
15 | }
16 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: gomod
4 | directory: "/utils"
5 | schedule:
6 | interval: daily
7 | timezone: Europe/London
8 | open-pull-requests-limit: 10
9 | rebase-strategy: disabled
10 | - package-ecosystem: github-actions
11 | directory: "/"
12 | schedule:
13 | interval: daily
14 | timezone: Europe/London
15 | open-pull-requests-limit: 10
16 | rebase-strategy: disabled
17 |
--------------------------------------------------------------------------------
/utils/git/limits_test.go:
--------------------------------------------------------------------------------
1 | package git
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestNewLimits(t *testing.T) {
11 | require.NoError(t, NoLimits().Validate()) //nolint:typecheck
12 | require.NoError(t, DefaultLimits().Validate()) //nolint:typecheck
13 | assert.True(t, DefaultLimits().Apply())
14 | assert.False(t, NoLimits().Apply())
15 | }
16 |
--------------------------------------------------------------------------------
/utils/filesystem/limits_test.go:
--------------------------------------------------------------------------------
1 | package filesystem
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestNewLimits(t *testing.T) {
11 | require.NoError(t, NoLimits().Validate()) //nolint:typecheck
12 | require.NoError(t, DefaultLimits().Validate()) //nolint:typecheck
13 | assert.True(t, DefaultLimits().Apply())
14 | assert.False(t, NoLimits().Apply())
15 | }
16 |
--------------------------------------------------------------------------------
/utils/subprocess/command_wrapper_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package subprocess
4 |
5 | import (
6 | "os/exec"
7 | "syscall"
8 | )
9 |
10 | func setGroupAttrToCmd(c *exec.Cmd) {
11 | c.SysProcAttr = &syscall.SysProcAttr{
12 | HideWindow: true,
13 | // Windows Process Creation Flags: https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
14 | CreationFlags: syscall.CREATE_UNICODE_ENVIRONMENT | syscall.CREATE_NEW_PROCESS_GROUP,
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/utils/logs/pipe_logger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/require"
11 | "go.uber.org/goleak"
12 | )
13 |
14 | func TestPipeLogger(t *testing.T) {
15 | defer goleak.VerifyNone(t)
16 | loggers, err := NewPipeLogger()
17 | require.NoError(t, err)
18 | testLog(t, loggers)
19 | }
20 |
--------------------------------------------------------------------------------
/utils/logs/noop_logger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/require"
11 | "go.uber.org/goleak"
12 | )
13 |
14 | func TestNoopLogger(t *testing.T) {
15 | defer goleak.VerifyNone(t)
16 | loggers, err := NewNoopLogger("Test")
17 | require.NoError(t, err)
18 | testLog(t, loggers)
19 | }
20 |
--------------------------------------------------------------------------------
/utils/platform/homedir_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package platform
4 |
5 | import (
6 | "fmt"
7 | "os"
8 |
9 | "github.com/ARM-software/golang-utils/utils/commonerrors"
10 | )
11 |
12 | func determineDefaultHomeDirectory(username string) (string, error) {
13 | drive := os.Getenv("HOMEDRIVE")
14 | if drive == "" {
15 | return "", fmt.Errorf("%w: cannot determine the default home drive", commonerrors.ErrUnexpected)
16 | }
17 | return fmt.Sprintf("%v\\Users\\%v", drive, username), nil
18 | }
19 |
--------------------------------------------------------------------------------
/changes/README.md:
--------------------------------------------------------------------------------
1 |
5 | # Changes directory
6 |
7 | This directory comprises information about all the changes that happened since the last release.
8 |
9 | A [news file](../CONTRIBUTING.md#news-files) should be added to this directory for each PR.
10 |
11 | On release of the package, the content of the file becomes part of the [change log](../CHANGELOG.md) and this directory is reset.
12 |
--------------------------------------------------------------------------------
/utils/units/multiplication/units.go:
--------------------------------------------------------------------------------
1 | // Package multiplication define common multiplication factor prefixes
2 | package multiplication
3 |
4 | const (
5 | Yotta = 1e24
6 | Zeta = 1e21
7 | Exa = 1e18
8 | Peta = 1e15
9 | Tera = 1e12
10 | Giga = 1e9
11 | Mega = 1e6
12 | Kilo = 1e3
13 | Hector = 1e2
14 | Deka = 1e1
15 | Deci = 1e-1
16 | Centi = 1e-2
17 | Milli = 1e-3
18 | Micro = 1e-6
19 | Nano = 1e-9
20 | Pico = 1e-12
21 | Femto = 1e-15
22 | Atto = 1e-18
23 | Zepto = 1e-21
24 | Yocto = 1e-24
25 | )
26 |
--------------------------------------------------------------------------------
/utils/platform/homedir_unix.go:
--------------------------------------------------------------------------------
1 | //go:build !darwin && !windows
2 |
3 | package platform
4 |
5 | import (
6 | "fmt"
7 | "os/user"
8 | "strings"
9 |
10 | "github.com/mitchellh/go-homedir"
11 | )
12 |
13 | func determineDefaultHomeDirectory(username string) (string, error) {
14 | currentDir, subErr1 := homedir.Dir()
15 | currentUser, subErr2 := user.Current()
16 | if subErr1 != nil || subErr2 != nil {
17 | return fmt.Sprintf("/home/%v", username), nil
18 | }
19 | return strings.ReplaceAll(currentDir, currentUser.Username, username), nil
20 | }
21 |
--------------------------------------------------------------------------------
/utils/http/headers/interfaces.go:
--------------------------------------------------------------------------------
1 | package headers
2 |
3 | import "net/http"
4 |
5 | //nolint:goimport
6 | //go:generate go tool mockgen -destination=../../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/http/$GOPACKAGE IHTTPHeaders
7 |
8 | // IHTTPHeaders defines an HTTP header.
9 | type IHTTPHeaders interface {
10 | AppendHeader(key, value string)
11 | Append(h *Header)
12 | Get(key string) string
13 | Has(h *Header) bool
14 | HasHeader(key string) bool
15 | Empty() bool
16 | AppendToResponse(w http.ResponseWriter)
17 | }
18 |
--------------------------------------------------------------------------------
/utils/logs/logrus_logger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/sirupsen/logrus"
11 | "github.com/stretchr/testify/require"
12 | "go.uber.org/goleak"
13 | )
14 |
15 | func TestLogrusLogger(t *testing.T) {
16 | defer goleak.VerifyNone(t)
17 | loggers, err := NewLogrusLogger(logrus.StandardLogger(), "Test")
18 | require.NoError(t, err)
19 | testLog(t, loggers)
20 | }
21 |
--------------------------------------------------------------------------------
/utils/logs/quiet_logger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2023 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/require"
11 | "go.uber.org/goleak"
12 | )
13 |
14 | func TestQuietLogger(t *testing.T) {
15 | defer goleak.VerifyNone(t)
16 | logger, err := NewStdLogger("Test")
17 | require.NoError(t, err)
18 | loggers, err := NewQuietLogger(logger)
19 | require.NoError(t, err)
20 | testLog(t, loggers)
21 | }
22 |
--------------------------------------------------------------------------------
/utils/config/fixtures/config-test.json:
--------------------------------------------------------------------------------
1 | {
2 | "dummy_string": "test string",
3 | "dummy_int": 1,
4 | "dummy_time": "54s",
5 | "dummyconfig": {
6 | "dummy_host": "host1",
7 | "port": 20,
8 | "db": "db1",
9 | "user": "user1",
10 | "password": "password1",
11 | "flag": true,
12 | "healthcheck_period": "1s"
13 | },
14 | "dummy_config": {
15 | "dummy_host": "host2",
16 | "port": 304,
17 | "db": "db2",
18 | "user": "user2",
19 | "password": "password2",
20 | "flag": false,
21 | "healthcheck_period": "24m"
22 | }
23 | }
--------------------------------------------------------------------------------
/utils/logs/slog_logger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "log/slog"
9 | "os"
10 | "testing"
11 |
12 | "github.com/stretchr/testify/require"
13 | "go.uber.org/goleak"
14 | )
15 |
16 | func TestSlogLogger(t *testing.T) {
17 | defer goleak.VerifyNone(t)
18 | logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
19 | loggers, err := NewSlogLogger(logger, "Test")
20 | require.NoError(t, err)
21 | testLog(t, loggers)
22 | }
23 |
--------------------------------------------------------------------------------
/utils/logs/pipe_logger.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2024 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "log"
9 | "os"
10 | )
11 |
12 | // NewPipeLogger will log messages without appending any prefix. Can be used when re-logging some other log output to prevent duplication of the log source etc.
13 | func NewPipeLogger() (loggers Loggers, err error) {
14 | loggers = &GenericLoggers{
15 | Output: log.New(os.Stdout, "", 0),
16 | Error: log.New(os.Stderr, "", 0),
17 | }
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/utils/platform/deletion_posix.go:
--------------------------------------------------------------------------------
1 | //go:build linux || unix || (js && wasm) || darwin || aix || dragonfly || freebsd || nacl || netbsd || openbsd || solaris
2 |
3 | package platform
4 |
5 | import (
6 | "context"
7 |
8 | "github.com/ARM-software/golang-utils/utils/subprocess/command"
9 | )
10 |
11 | func removeFileAs(ctx context.Context, as *command.CommandAsDifferentUser, path string) error {
12 | return executeCommandAs(ctx, as, "rm", "-f")
13 | }
14 |
15 | func removeDirAs(ctx context.Context, as *command.CommandAsDifferentUser, path string) error {
16 | return executeCommandAs(ctx, as, "rm", "-r", "-f")
17 | }
18 |
--------------------------------------------------------------------------------
/utils/safeio/error_test.go:
--------------------------------------------------------------------------------
1 | package safeio
2 |
3 | import (
4 | "errors"
5 | "io"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 |
10 | "github.com/ARM-software/golang-utils/utils/commonerrors"
11 | "github.com/ARM-software/golang-utils/utils/commonerrors/errortest"
12 | )
13 |
14 | func Test_convertIOError(t *testing.T) {
15 | assert.NoError(t, ConvertIOError(nil))
16 | err := errors.New("test")
17 | assert.ErrorIs(t, err, ConvertIOError(err))
18 |
19 | errortest.AssertError(t, ConvertIOError(commonerrors.ErrEOF), commonerrors.ErrEOF)
20 | errortest.AssertError(t, ConvertIOError(io.EOF), commonerrors.ErrEOF)
21 | }
22 |
--------------------------------------------------------------------------------
/utils/subprocess/supervisor/interface.go:
--------------------------------------------------------------------------------
1 | package supervisor
2 |
3 | import "context"
4 |
5 | //go:generate go tool mockgen -destination=../../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/subprocess/$GOPACKAGE ISupervisor
6 |
7 | // ISupervisor will run a command and automatically restart it if it exits. Hooks can be used to execute code at
8 | // different points in the execution lifecyle. Restarts can be delayed
9 | type ISupervisor interface {
10 | // Run will run the supervisor and execute any of the command hooks. If it receives a halting error or the context is cancelled then it will exit
11 | Run(ctx context.Context) error
12 | }
13 |
--------------------------------------------------------------------------------
/utils/encryption/interface.go:
--------------------------------------------------------------------------------
1 | // Package encryption defines utilities with regards to cryptography.
2 | package encryption
3 |
4 | import (
5 | "encoding/json"
6 | "fmt"
7 | )
8 |
9 | //go:generate go tool mockgen -destination=../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/$GOPACKAGE IKeyPair
10 |
11 | // IKeyPair defines an asymmetric key pair for cryptography.
12 | type IKeyPair interface {
13 | fmt.Stringer
14 | fmt.GoStringer
15 | json.Marshaler
16 | // GetPublicKey returns the public key (base64 encoded)
17 | GetPublicKey() string
18 | // GetPrivateKey returns the private key (base64 encoded)
19 | GetPrivateKey() string
20 | }
21 |
--------------------------------------------------------------------------------
/utils/idgen/uuid.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package idgen
6 |
7 | import (
8 | "github.com/gofrs/uuid/v5"
9 |
10 | "github.com/ARM-software/golang-utils/utils/commonerrors"
11 | )
12 |
13 | // Generates a UUID.
14 | func GenerateUUID4() (string, error) {
15 | uuid, err := uuid.NewV4()
16 | if err != nil {
17 | return "", commonerrors.WrapError(commonerrors.ErrUnexpected, err, "failed generating uuid")
18 | }
19 | return uuid.String(), nil
20 | }
21 |
22 | func IsValidUUID(u string) bool {
23 | _, err := uuid.FromString(u)
24 | return err == nil
25 | }
26 |
--------------------------------------------------------------------------------
/utils/resource/interfaces.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | // Package resource resource defines utilities about generic resources.
7 | package resource
8 |
9 | import (
10 | "fmt"
11 | "io"
12 | )
13 |
14 | //go:generate go tool mockgen -destination=../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/$GOPACKAGE ICloseableResource
15 |
16 | // ICloseableResource defines a resource which must be closed after use e.g. an open file.
17 | type ICloseableResource interface {
18 | io.Closer
19 | fmt.Stringer
20 | IsClosed() bool
21 | }
22 |
--------------------------------------------------------------------------------
/utils/filesystem/files_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | // Package filesystem describes the filesystem on windows
4 | package filesystem
5 |
6 | import (
7 | "os"
8 | "syscall"
9 |
10 | "github.com/ARM-software/golang-utils/utils/commonerrors"
11 | )
12 |
13 | func isPrivilegeError(err error) bool {
14 | return commonerrors.Any(err, syscall.EPERM, syscall.ERROR_PRIVILEGE_NOT_HELD)
15 | }
16 |
17 | func isNotSupportedError(err error) bool {
18 | return commonerrors.Any(err, syscall.ENOTSUP, syscall.EOPNOTSUPP, syscall.EWINDOWS)
19 | }
20 |
21 | func determineFileOwners(_ os.FileInfo) (uid, gid int, err error) {
22 | uid = syscall.Getuid()
23 | gid = syscall.Getgid()
24 |
25 | return
26 | }
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/utils/hashing/interfaces.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | // Package hashing provides utilities for calculating hashes.
7 | package hashing
8 |
9 | import (
10 | "context"
11 | "io"
12 | )
13 |
14 | //go:generate go tool mockgen -destination=../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/$GOPACKAGE IHash
15 |
16 | // IHash defines a hashing algorithm.
17 | type IHash interface {
18 | Calculate(reader io.Reader) (string, error)
19 | CalculateWithContext(ctx context.Context, reader io.Reader) (string, error)
20 | GetType() string
21 | }
22 |
--------------------------------------------------------------------------------
/utils/filesystem/iofs_test.go:
--------------------------------------------------------------------------------
1 | package filesystem
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/go-faker/faker/v4"
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func Test_IoFS_Exists(t *testing.T) {
12 | ifs, err := NewEmbedFileSystem(&testContent)
13 | require.NoError(t, err)
14 |
15 | ioFs, err := ConvertToIOFilesystem(ifs)
16 | require.NoError(t, err)
17 |
18 | fs, err := ConvertFromIOFilesystem(ioFs)
19 | require.NoError(t, err)
20 |
21 | assert.False(t, fs.Exists(faker.DomainName()))
22 | assert.True(t, fs.Exists("testdata"))
23 | assert.True(t, fs.Exists("testdata/embed"))
24 | assert.True(t, fs.Exists("testdata/embed/level1/test.txt"))
25 | }
26 |
--------------------------------------------------------------------------------
/utils/http/common.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package http
6 |
7 | import "net/http"
8 |
9 | func setTransportConfiguration(cfg *HTTPClientConfiguration, transport *http.Transport) {
10 | if cfg == nil || transport == nil {
11 | return
12 | }
13 | transport.IdleConnTimeout = cfg.IdleConnTimeout
14 | transport.ExpectContinueTimeout = cfg.ExpectContinueTimeout
15 | transport.TLSHandshakeTimeout = cfg.TLSHandshakeTimeout
16 | transport.MaxIdleConns = cfg.MaxIdleConns
17 | transport.MaxConnsPerHost = cfg.MaxConnsPerHost
18 | transport.MaxIdleConnsPerHost = cfg.MaxIdleConnsPerHost
19 | }
20 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
5 | ### Description
6 |
7 |
10 |
11 |
12 |
13 | ### Test Coverage
14 |
15 |
18 |
19 | - [ ] This change is covered by existing or additional automated tests.
20 | - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible.
21 | - [ ] Additional tests are not required for this change (e.g. documentation update).
22 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | minimum_pre_commit_version: 1.7.0
2 | repos:
3 | - repo: local
4 | hooks:
5 | - id: golangci-lint
6 | name: golangci-lint
7 | description: Fast linters runner for Go.
8 | # Disabling Go module support is necessary to avoid it complaining about a missing go.mod file in the root
9 | entry: env GO111MODULE=off golangci-lint run --config=.golangci.pre-commit.yaml ./...
10 | language: system
11 | types: [go]
12 | pass_filenames: false
13 |
14 | - repo: https://github.com/golangci/misspell
15 | rev: v0.6.0
16 | hooks:
17 | - id: misspell
18 |
19 | - repo: https://github.com/ARMmbed/continuous-delivery-scripts.git
20 | rev: 2.5.0
21 | hooks:
22 | - id: licensing
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
5 | ### Description
6 |
7 |
14 |
15 |
16 |
17 | ### Issue request type
18 |
19 |
23 |
24 | - [ ] Enhancement
25 | - [ ] Bug
26 | - [ ] Question
27 |
--------------------------------------------------------------------------------
/utils/logs/std_logger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "testing"
9 | "time"
10 |
11 | "github.com/stretchr/testify/require"
12 | "go.uber.org/goleak"
13 | )
14 |
15 | func TestStdLogger(t *testing.T) {
16 | defer goleak.VerifyNone(t)
17 | loggers, err := NewStdLogger("Test")
18 | require.NoError(t, err)
19 | testLog(t, loggers)
20 | }
21 |
22 | func TestAsynchronousStdLogger(t *testing.T) {
23 | defer goleak.VerifyNone(t)
24 | loggers, err := NewAsynchronousStdLogger("Test", 1024, 2*time.Millisecond, "test source")
25 | require.NoError(t, err)
26 | testLog(t, loggers)
27 | }
28 |
--------------------------------------------------------------------------------
/utils/logs/slog_logger.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | // Package logs defines loggers for use in projects.
7 | package logs
8 |
9 | import (
10 | "log/slog"
11 |
12 | "github.com/ARM-software/golang-utils/utils/commonerrors"
13 | "github.com/ARM-software/golang-utils/utils/logs/logrimp"
14 | )
15 |
16 | // NewSlogLogger returns a logger which uses slog logger (standard library package)
17 | func NewSlogLogger(slogL *slog.Logger, loggerSource string) (loggers Loggers, err error) {
18 | if slogL == nil {
19 | err = commonerrors.ErrNoLogger
20 | return
21 | }
22 | return NewLogrLogger(logrimp.NewSlogLogger(slogL), loggerSource)
23 | }
24 |
--------------------------------------------------------------------------------
/utils/platform/deletion_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package platform
4 |
5 | import (
6 | "context"
7 |
8 | "github.com/ARM-software/golang-utils/utils/subprocess/command"
9 | )
10 |
11 | func removeFileAs(ctx context.Context, as *command.CommandAsDifferentUser, path string) error {
12 | err := executeCommandAs(ctx, as, "takeown", "/f", path)
13 | if err != nil {
14 | return err
15 | }
16 | return executeCommandAs(ctx, as, "del", "/q", "/f", path)
17 | }
18 |
19 | func removeDirAs(ctx context.Context, as *command.CommandAsDifferentUser, path string) error {
20 | err := executeCommandAs(ctx, as, "takeown", "/r", "/d", "Y", "/f", path)
21 | if err != nil {
22 | return err
23 | }
24 | return executeCommandAs(ctx, as, "rmdir", "/s", "/q", path)
25 | }
26 |
--------------------------------------------------------------------------------
/utils/config/interfaces.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | // Package config provides utilities to load configuration from an environment and perform validation at load time.
7 | package config
8 |
9 | //go:generate go tool mockgen -destination=../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/$GOPACKAGE IServiceConfiguration,Validator
10 |
11 | // IServiceConfiguration defines a typical service configuration.
12 | type IServiceConfiguration interface {
13 | Validator
14 | }
15 |
16 | // Validator defines an object which can perform some validation on itself.
17 | type Validator interface {
18 | Validate() error
19 | }
20 |
--------------------------------------------------------------------------------
/utils/http/retry.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/go-logr/logr"
7 |
8 | "github.com/ARM-software/golang-utils/utils/commonerrors"
9 | "github.com/ARM-software/golang-utils/utils/retry"
10 | )
11 |
12 | // RetryOnError allows the caller to retry fn when the error returned by fn is retriable
13 | // as in of the type specified by retriableErr. backoff defines the maximum retries and the wait
14 | // interval between two retries.
15 | func RetryOnError(ctx context.Context, logger logr.Logger, retryPolicy *RetryPolicyConfiguration, fn func() error, msgOnRetry string, retriableErr ...error) error {
16 | return retry.RetryIf(ctx, logger, retryPolicy, fn, msgOnRetry, func(err error) bool {
17 | return commonerrors.Any(err, retriableErr...)
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/utils/logs/logrimp/stdout.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | // Package logrimp defines some common logr implementation
7 | package logrimp
8 |
9 | import (
10 | "fmt"
11 |
12 | "github.com/go-logr/logr"
13 | "github.com/go-logr/logr/funcr"
14 | )
15 |
16 | // NewStdOutLogr returns a logger to standard out.
17 | // See https://github.com/go-logr/logr/blob/ff91da8dc418a9e36998931ed4ab10b71833a368/example_test.go#L27
18 | func NewStdOutLogr() logr.Logger {
19 | return funcr.New(func(prefix, args string) {
20 | if prefix != "" {
21 | fmt.Printf("%s: %s\n", prefix, args)
22 | } else {
23 | fmt.Println(args)
24 | }
25 | }, funcr.Options{})
26 | }
27 |
--------------------------------------------------------------------------------
/utils/resource/resource_test.go:
--------------------------------------------------------------------------------
1 | package resource
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func Test_CloseableResource(t *testing.T) {
11 | desc := "test nil resource"
12 | testResource := NewCloseableResource(nil, desc)
13 | assert.Contains(t, testResource.String(), desc)
14 | assert.False(t, testResource.IsClosed())
15 | require.NoError(t, testResource.Close())
16 | assert.True(t, testResource.IsClosed())
17 | }
18 |
19 | func Test_NonCloseableResource(t *testing.T) {
20 | testResource := NewNonCloseableResource()
21 | assert.NotEmpty(t, testResource.String())
22 | assert.False(t, testResource.IsClosed())
23 | require.NoError(t, testResource.Close())
24 | assert.False(t, testResource.IsClosed())
25 | }
26 |
--------------------------------------------------------------------------------
/utils/semver/sanitisation.go:
--------------------------------------------------------------------------------
1 | package semver
2 |
3 | import (
4 | "golang.org/x/mod/semver"
5 | )
6 |
7 | // SanitiseVersionMajorMinor will return the major and minor parts of the version with the 'v' prefix
8 | func SanitiseVersionMajorMinor(version string) (majorMinor string, err error) {
9 | version, err = CanonicalWithGoPrefix(version)
10 | if err != nil {
11 | return
12 | }
13 |
14 | majorMinor = TrimGoPrefix(semver.MajorMinor(version))
15 | return
16 | }
17 |
18 | // SanitiseVersionMajor will return the major part of the version with the 'v' prefix
19 | func SanitiseVersionMajor(version string) (majorMinor string, err error) {
20 | version, err = CanonicalWithGoPrefix(version)
21 | if err != nil {
22 | return
23 | }
24 |
25 | majorMinor = TrimGoPrefix(semver.Major(version))
26 | return
27 | }
28 |
--------------------------------------------------------------------------------
/.golangci.pre-commit.yaml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | run:
3 | build-tags:
4 | - integration
5 | linters:
6 | default: none
7 | exclusions:
8 | generated: lax
9 | presets:
10 | - comments
11 | - common-false-positives
12 | - legacy
13 | - std-error-handling
14 | paths:
15 | - third_party$
16 | - builtin$
17 | - examples$
18 | issues:
19 | fix: true
20 | formatters:
21 | enable:
22 | - gci
23 | - goimports
24 | settings:
25 | gci:
26 | sections:
27 | - Standard
28 | - Default
29 | - Prefix(github.com/ARM-software)
30 | goimports:
31 | local-prefixes:
32 | - github.com/ARM-software
33 | exclusions:
34 | generated: lax
35 | paths:
36 | - third_party$
37 | - builtin$
38 | - examples$
39 |
--------------------------------------------------------------------------------
/utils/commonerrors/errortest/testing_test.go:
--------------------------------------------------------------------------------
1 | package errortest
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/ARM-software/golang-utils/utils/commonerrors"
7 | )
8 |
9 | func TestAssertError(t *testing.T) {
10 | AssertError(t, commonerrors.ErrUndefined, commonerrors.ErrNotFound, commonerrors.ErrMarshalling, commonerrors.ErrUndefined)
11 | }
12 |
13 | func TestAssertErrorDescription(t *testing.T) {
14 | AssertErrorDescription(t, commonerrors.ErrUndefined, "undefined", "not found")
15 | }
16 |
17 | func TestRequireError(t *testing.T) {
18 | RequireError(t, commonerrors.ErrUndefined, commonerrors.ErrNotFound, commonerrors.ErrMarshalling, commonerrors.ErrUndefined)
19 | }
20 |
21 | func TestRequireErrorContent(t *testing.T) {
22 | RequireErrorDescription(t, commonerrors.ErrUndefined, "undefined", "not found")
23 | }
24 |
--------------------------------------------------------------------------------
/utils/serialization/xml.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package serialization //nolint:misspell
6 |
7 | import (
8 | "bytes"
9 | "encoding/xml"
10 |
11 | "golang.org/x/net/html/charset"
12 | )
13 |
14 | // UnmarshallXML was introduced instead
15 | // of using xml.Unmarshal() as this only supports UTF8
16 | // But it's been noticed that UnmarshalXml doesn't support UTF16
17 | func UnmarshallXML(data []byte, value interface{}) error {
18 | // Read the XML file and create an in-memory model constructed from the
19 | // elements in the data
20 | reader := bytes.NewReader(data)
21 | decoder := xml.NewDecoder(reader)
22 |
23 | decoder.CharsetReader = charset.NewReaderLabel
24 | return decoder.Decode(&value)
25 | }
26 |
--------------------------------------------------------------------------------
/utils/charset/unsupportedcharset_index.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package charset
6 |
7 | import (
8 | "strings"
9 |
10 | "golang.org/x/text/encoding"
11 |
12 | "github.com/ARM-software/golang-utils/utils/commonerrors"
13 | )
14 |
15 | var (
16 | unsupportedCharsets = []string{"UTF-7-IMAP"}
17 | )
18 |
19 | // GetUnsupported gets valid IANA charset encoding we know are not supported by golang but not reported as such.
20 | func GetUnsupported(name string) (encoding.Encoding, error) {
21 | for i := range unsupportedCharsets {
22 | if strings.EqualFold(unsupportedCharsets[i], name) {
23 | return nil, nil
24 | }
25 | }
26 | return nil, commonerrors.New(commonerrors.ErrInvalid, "invalid encoding name")
27 | }
28 |
--------------------------------------------------------------------------------
/utils/safeio/error.go:
--------------------------------------------------------------------------------
1 | package safeio
2 |
3 | import (
4 | "io"
5 | "os"
6 |
7 | "github.com/ARM-software/golang-utils/utils/commonerrors"
8 | )
9 |
10 | // ConvertIOError converts an I/O error into common errors.
11 | func ConvertIOError(err error) (newErr error) {
12 | if err == nil {
13 | return
14 | }
15 | newErr = commonerrors.ConvertContextError(err)
16 | switch {
17 | case commonerrors.Any(newErr, commonerrors.ErrEOF):
18 | case commonerrors.Any(newErr, io.EOF, io.ErrUnexpectedEOF):
19 | newErr = commonerrors.WrapError(commonerrors.ErrEOF, newErr, "")
20 | case commonerrors.Any(newErr, os.ErrClosed):
21 | // cancelling a reader on a copy will cause it to close the file and return os.ErrClosed so map it to cancelled for this package
22 | newErr = commonerrors.WrapError(commonerrors.ErrCancelled, newErr, "")
23 | }
24 | return
25 | }
26 |
--------------------------------------------------------------------------------
/utils/logs/zap_logger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/require"
11 | "go.uber.org/goleak"
12 | "go.uber.org/zap"
13 | )
14 |
15 | func TestZapLoggerDev(t *testing.T) {
16 | defer goleak.VerifyNone(t)
17 | logger, err := zap.NewDevelopment()
18 | require.NoError(t, err)
19 | loggers, err := NewZapLogger(logger, "Test")
20 | require.NoError(t, err)
21 | testLog(t, loggers)
22 | }
23 |
24 | func TestZapLoggerProd(t *testing.T) {
25 | defer goleak.VerifyNone(t)
26 | logger, err := zap.NewProduction()
27 | require.NoError(t, err)
28 | loggers, err := NewZapLogger(logger, "Test")
29 | require.NoError(t, err)
30 | testLog(t, loggers)
31 | }
32 |
--------------------------------------------------------------------------------
/utils/filesystem/files_posix.go:
--------------------------------------------------------------------------------
1 | //go:build linux || unix || (js && wasm) || darwin || aix || dragonfly || freebsd || nacl || netbsd || openbsd || solaris
2 |
3 | package filesystem
4 |
5 | import (
6 | "os"
7 | "syscall"
8 |
9 | "github.com/ARM-software/golang-utils/utils/commonerrors"
10 | )
11 |
12 | func isPrivilegeError(err error) bool {
13 | return commonerrors.Any(err, syscall.EPERM)
14 | }
15 |
16 | func isNotSupportedError(err error) bool {
17 | return commonerrors.Any(err, syscall.ENOTSUP, syscall.EOPNOTSUPP)
18 | }
19 |
20 | func determineFileOwners(info os.FileInfo) (uid, gid int, err error) {
21 | if raw, ok := info.Sys().(*syscall.Stat_t); ok {
22 | gid = int(raw.Gid)
23 | uid = int(raw.Uid)
24 | } else {
25 | err = commonerrors.Newf(commonerrors.ErrUnsupported, "file info [%v] is not of type Stat_t", info.Sys())
26 | }
27 | return
28 | }
29 |
--------------------------------------------------------------------------------
/utils/safecast/ReadMe.md:
--------------------------------------------------------------------------------
1 | # Safecast
2 |
3 | the purpose of this utilities is to perform safe number conversion in go similarly to [go-safecast](https://github.com/ccoVeille/go-safecast) from which they are inspired from.
4 | It should help tackling gosec [G115 rule](https://github.com/securego/gosec/pull/1149)
5 |
6 | G115: Potential overflow when converting between integer types.
7 |
8 | and [CWE-190](https://cwe.mitre.org/data/definitions/190.html)
9 |
10 |
11 | infinite loop
12 | access to wrong resource by id
13 | grant access to someone who exhausted their quota
14 |
15 | Contrary to `go-safecast` no error is returned when attempting casting and the MAX or MIN value of the type is returned instead if the value is beyond the allowed window.
16 | For instance, `toInt8(255)-> 127` and `toInt8(-255)-> -128`
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/utils/logs/logstest/testing.go:
--------------------------------------------------------------------------------
1 | package logstest
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/bombsimon/logrusr/v4"
7 | "github.com/go-logr/logr"
8 | "github.com/go-logr/logr/testr"
9 | logrusTest "github.com/sirupsen/logrus/hooks/test"
10 |
11 | "github.com/ARM-software/golang-utils/utils/logs/logrimp"
12 | )
13 |
14 | // NewNullTestLogger returns a logger to nothing
15 | func NewNullTestLogger() logr.Logger {
16 | // FIXME should probably be replaced by NoOp
17 | internalLogger, _ := logrusTest.NewNullLogger()
18 | return logrusr.New(internalLogger)
19 | }
20 |
21 | // NewStdTestLogger returns a test logger to standard output.
22 | func NewStdTestLogger() logr.Logger {
23 | return logrimp.NewStdOutLogr()
24 | }
25 |
26 | // NewTestLogger returns a logger to use in tests
27 | func NewTestLogger(t *testing.T) logr.Logger {
28 | return testr.New(t)
29 | }
30 |
--------------------------------------------------------------------------------
/utils/platform/deletion_test.go:
--------------------------------------------------------------------------------
1 | package platform
2 |
3 | import (
4 | "context"
5 | "os"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestRemoveWithPrivileges(t *testing.T) {
13 | t.Skip("Can only be run as administrator")
14 | tmpDir := t.TempDir()
15 | defer func() { _ = os.RemoveAll(tmpDir) }()
16 |
17 | dir, err := os.MkdirTemp(tmpDir, "testDirToRemove")
18 | require.NoError(t, err)
19 |
20 | assert.NoError(t, RemoveWithPrivileges(context.TODO(), dir))
21 |
22 | _, err = os.Stat(dir)
23 | assert.Error(t, err)
24 |
25 | f, err := os.CreateTemp(tmpDir, "testfile")
26 | require.NoError(t, err)
27 | err = f.Close()
28 | require.NoError(t, err)
29 |
30 | assert.NoError(t, RemoveWithPrivileges(context.TODO(), f.Name()))
31 |
32 | _, err = os.Stat(f.Name())
33 | assert.Error(t, err)
34 | }
35 |
--------------------------------------------------------------------------------
/utils/filesystem/filesystem_test.go:
--------------------------------------------------------------------------------
1 | package filesystem
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "testing"
8 |
9 | "github.com/go-faker/faker/v4"
10 |
11 | "github.com/ARM-software/golang-utils/utils/commonerrors"
12 | "github.com/ARM-software/golang-utils/utils/commonerrors/errortest"
13 | )
14 |
15 | func TestConvertFileSystemError(t *testing.T) {
16 | errortest.AssertError(t, ConvertFileSystemError(errors.New("write /tmp/dot-env932398628/.env.896898355: bad file descriptor")), commonerrors.ErrConflict, commonerrors.ErrCondition)
17 | errortest.AssertError(t, ConvertFileSystemError(fmt.Errorf("write %v: bad file descriptor", faker.Sentence())), commonerrors.ErrConflict, commonerrors.ErrCondition)
18 | errortest.AssertError(t, ConvertFileSystemError(nil), nil)
19 | errortest.AssertError(t, ConvertFileSystemError(context.Canceled), commonerrors.ErrCancelled)
20 | }
21 |
--------------------------------------------------------------------------------
/utils/strings/strings.go:
--------------------------------------------------------------------------------
1 | package strings
2 |
3 | import (
4 | "math"
5 | "strings"
6 |
7 | "github.com/ARM-software/golang-utils/utils/reflection"
8 | )
9 |
10 | // CalculateStringShannonEntropy measures the Shannon entropy of a string
11 | // the returned value is a bits/byte 'entropy' value between 0.0 and 8.0,
12 | // 0 being no, and 8 being maximal entropy.
13 | // See http://bearcave.com/misl/misl_tech/wavelets/compression/shannon.html for the algorithmic explanation.
14 | // Code comes from https://rosettacode.org/wiki/Entropy#Go:_Slice_version
15 | func CalculateStringShannonEntropy(str string) (entropy float64) {
16 | if reflection.IsEmpty(str) {
17 | return 0
18 | }
19 | for i := 0; i < 256; i++ {
20 | px := float64(strings.Count(str, string(byte(i)))) / float64(len(str))
21 | if px > 0 {
22 | entropy += -px * math.Log2(px)
23 | }
24 | }
25 | return entropy
26 | }
27 |
--------------------------------------------------------------------------------
/utils/proc/find/find.go:
--------------------------------------------------------------------------------
1 | package find
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "regexp"
7 |
8 | "github.com/ARM-software/golang-utils/utils/commonerrors"
9 | "github.com/ARM-software/golang-utils/utils/proc"
10 | )
11 |
12 | const numWorkers = 10
13 |
14 | // FindProcessByRegex will search for the processes that match a specific regex
15 | func FindProcessByRegex(ctx context.Context, re *regexp.Regexp) (processes []proc.IProcess, err error) {
16 | if re == nil {
17 | err = commonerrors.UndefinedVariable("regex to search")
18 | return
19 | }
20 | return findProcessByRegex(ctx, re)
21 | }
22 |
23 | // FindProcessByName will search for the processes that match a specific name
24 | func FindProcessByName(ctx context.Context, name string) (processes []proc.IProcess, err error) {
25 | return FindProcessByRegex(ctx, regexp.MustCompile(fmt.Sprintf(".*%v.*", regexp.QuoteMeta(name))))
26 | }
27 |
--------------------------------------------------------------------------------
/utils/sharedcache/factory.go:
--------------------------------------------------------------------------------
1 | package sharedcache
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ARM-software/golang-utils/utils/commonerrors"
7 | "github.com/ARM-software/golang-utils/utils/filesystem"
8 | )
9 |
10 | //go:generate go run github.com/dmarkham/enumer -type=CacheType -text -json -yaml
11 | type CacheType int
12 |
13 | const (
14 | CacheMutable CacheType = iota
15 | CacheImmutable
16 | )
17 |
18 | var (
19 | CacheTypes = []CacheType{CacheMutable, CacheImmutable}
20 | )
21 |
22 | func NewCache(cacheType CacheType, fs filesystem.FS, cfg *Configuration) (ISharedCacheRepository, error) {
23 | switch cacheType {
24 | case CacheMutable:
25 | return NewSharedMutableCacheRepository(cfg, fs)
26 | case CacheImmutable:
27 | return NewSharedImmutableCacheRepository(cfg, fs)
28 | }
29 | return nil, fmt.Errorf("%w: unknown cache type [%v]", commonerrors.ErrNotFound, cacheType)
30 | }
31 |
--------------------------------------------------------------------------------
/utils/signing/interface.go:
--------------------------------------------------------------------------------
1 | package signing
2 |
3 | import "github.com/ARM-software/golang-utils/utils/encryption"
4 |
5 | type ICodeSigner interface {
6 | encryption.IKeyPair
7 | // Sign will sign a message and return a signature
8 | Sign(message []byte) (signature []byte, err error)
9 | // Verify will take a message and a signature and verify whether the signature is a valid signature of the message based on the signers public key
10 | Verify(message, signature []byte) (ok bool, err error)
11 | // GenerateSignature will sign a message but return a base64 encoded signature for ease of use
12 | GenerateSignature(message []byte) (signatureBase64 string, err error)
13 | // Verify will take a message and a base64 encoded signature and verify whether the signature is a valid signature of the message based on the signers public key
14 | VerifySignature(message []byte, signatureBase64 string) (ok bool, err error)
15 | }
16 |
--------------------------------------------------------------------------------
/utils/platform/homedir_test.go:
--------------------------------------------------------------------------------
1 | package platform
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/go-faker/faker/v4"
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestGetDefaultHomeDirectory(t *testing.T) {
13 | username := faker.Username()
14 | home, err := GetDefaultHomeDirectory(username)
15 | require.NoError(t, err)
16 | assert.NotEmpty(t, home)
17 | assert.Contains(t, home, username)
18 | }
19 |
20 | func TestGetHomeDirectory(t *testing.T) {
21 | username := faker.Username()
22 | home, err := GetHomeDirectory(username)
23 | require.NoError(t, err)
24 | assert.NotEmpty(t, home)
25 | assert.Contains(t, home, username)
26 |
27 | currentUser, err := GetCurrentUser()
28 | require.NoError(t, err)
29 | home, err = GetHomeDirectory(currentUser.Username)
30 | fmt.Println(home)
31 | require.NoError(t, err)
32 | assert.NotEmpty(t, home)
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/utils/config/fixtures/env-test.env:
--------------------------------------------------------------------------------
1 | # These env vars will map to a struct the embeds another struct with no mapstructure tag set (it will expect its struct name to be part of the env var)
2 | WITH_NO_TAG_TESTBASECFG_EMBEDDED1="embedded 1"
3 | WITH_NO_TAG_TESTBASECFG_EMBEDDED2="embedded 2"
4 | WITH_NO_TAG_NON_EMBEDDED1="non-embedded 1"
5 | WITH_NO_TAG_NON_EMBEDDED2="non-embedded 2"
6 | # These env vars will map to a struct the embeds another struct with a mapstructure tag set
7 | WITH_TAG_EMBEDDED_STRUCT_EMBEDDED1="embedded 1"
8 | WITH_TAG_EMBEDDED_STRUCT_EMBEDDED2="embedded 2"
9 | WITH_TAG_NON_EMBEDDED1="non-embedded 1"
10 | WITH_TAG_NON_EMBEDDED2="non-embedded 2"
11 | # These env vars will map to a struct the embeds another struct with the ",squash" mapstructure tag set
12 | WITH_SQUASH_TAG_EMBEDDED1="embedded 1"
13 | WITH_SQUASH_TAG_EMBEDDED2="embedded 2"
14 | WITH_SQUASH_TAG_NON_EMBEDDED1="non-embedded 1"
15 | WITH_SQUASH_TAG_NON_EMBEDDED2="non-embedded 2"
--------------------------------------------------------------------------------
/utils/logs/logstest/testing_test.go:
--------------------------------------------------------------------------------
1 | package logstest
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/go-faker/faker/v4"
7 |
8 | "github.com/ARM-software/golang-utils/utils/commonerrors"
9 | )
10 |
11 | func TestNewNullTestLogger(t *testing.T) {
12 | logger := NewNullTestLogger()
13 | logger.WithValues("foo", "bar").Info(faker.Sentence())
14 | logger.Error(commonerrors.ErrUnexpected, faker.Sentence(), faker.Word(), faker.Name())
15 | }
16 |
17 | func TestNewTestLogger(t *testing.T) {
18 | logger := NewTestLogger(t)
19 | logger.Info(faker.Sentence())
20 | logger.Info(faker.Sentence(), "foo", "bar")
21 | logger.Error(commonerrors.ErrUnexpected, faker.Sentence(), faker.Word(), faker.Name())
22 | }
23 |
24 | func TestNewStdTestLogger(t *testing.T) {
25 | logger := NewStdTestLogger()
26 | logger.WithValues("foo", "bar").Info(faker.Sentence())
27 | logger.Error(commonerrors.ErrUnexpected, faker.Sentence(), faker.Word(), faker.Name())
28 | }
29 |
--------------------------------------------------------------------------------
/utils/idgen/uuid_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package idgen
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | "github.com/stretchr/testify/require"
12 | )
13 |
14 | func TestUuidUniqueness(t *testing.T) {
15 | uuid1, err := GenerateUUID4()
16 | require.NoError(t, err)
17 |
18 | uuid2, err := GenerateUUID4()
19 | require.NoError(t, err)
20 |
21 | assert.NotEqual(t, uuid1, uuid2)
22 | }
23 |
24 | func TestUuidLength(t *testing.T) {
25 | uuid, err := GenerateUUID4()
26 | require.NoError(t, err)
27 |
28 | assert.Equal(t, 36, len(uuid))
29 | }
30 |
31 | func TestInvalidUUID(t *testing.T) {
32 | id := "1"
33 | require.False(t, IsValidUUID(id))
34 | }
35 |
36 | func TestValidUUID(t *testing.T) {
37 | id := "5a4b6bb3-0bd3-4c4e-ba4c-45658cca4289"
38 | require.True(t, IsValidUUID(id))
39 | }
40 |
--------------------------------------------------------------------------------
/utils/platform/cmd_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package platform
4 |
5 | import (
6 | "github.com/ARM-software/golang-utils/utils/subprocess/command"
7 | )
8 |
9 | var (
10 | // runAsAdmin describes the command to use to run command as Administrator
11 | // See https://ss64.com/nt/syntax-elevate.html
12 | // see https://lazyadmin.nl/it/runas-command/
13 | // see https://www.tenforums.com/general-support/111929-how-use-runas-without-password-prompt.html
14 | runAsAdmin = command.RunAs("Administrator")
15 | )
16 |
17 | // DefineRunAsAdmin defines the command to run as Administrator.
18 | // e.g.
19 | // - args="runas", "/user:adrien" to run commands as `adrien`
20 | func DefineRunAsAdmin(args ...string) {
21 | runAsAdmin = command.NewCommandAsDifferentUser(args...)
22 | }
23 |
24 | func getRunCommandWithPrivileges() *command.CommandAsDifferentUser {
25 | return runAsAdmin
26 | }
27 |
28 | func hasPasswordlessPrivileges() bool {
29 | return true
30 | }
31 |
--------------------------------------------------------------------------------
/utils/safeio/write_test.go:
--------------------------------------------------------------------------------
1 | package safeio
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "testing"
7 |
8 | "github.com/go-faker/faker/v4"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 |
12 | "github.com/ARM-software/golang-utils/utils/commonerrors"
13 | "github.com/ARM-software/golang-utils/utils/commonerrors/errortest"
14 | )
15 |
16 | func TestWriteString(t *testing.T) {
17 | var buf bytes.Buffer
18 | text := faker.Sentence()
19 | n, err := WriteString(context.Background(), &buf, text)
20 | require.NoError(t, err)
21 | assert.Equal(t, len(text), n)
22 | assert.Equal(t, text, buf.String())
23 | buf.Reset()
24 |
25 | ctx, cancel := context.WithCancel(context.Background())
26 | defer cancel()
27 |
28 | cancel()
29 | n, err = WriteString(ctx, &buf, text)
30 | require.Error(t, err)
31 | errortest.AssertError(t, err, commonerrors.ErrCancelled)
32 | assert.Zero(t, n)
33 | assert.Empty(t, buf.String())
34 | }
35 |
--------------------------------------------------------------------------------
/utils/safecast/boundary.go:
--------------------------------------------------------------------------------
1 | package safecast
2 |
3 | func greaterThanUpperBoundary[C1 IConvertable, C2 IConvertable](value C1, upperBoundary C2) (greater bool) {
4 | if value <= 0 {
5 | return
6 | }
7 |
8 | switch f := any(value).(type) {
9 | case float64:
10 | greater = f >= float64(upperBoundary)
11 | case float32:
12 | greater = float64(f) >= float64(upperBoundary)
13 | default:
14 | // for all other integer types, it fits in an uint64 without overflow as we know value is positive.
15 | greater = uint64(value) > uint64(upperBoundary)
16 | }
17 |
18 | return
19 | }
20 |
21 | func lessThanLowerBoundary[T IConvertable, T2 IConvertable](value T, boundary T2) (lower bool) {
22 | if value >= 0 {
23 | return
24 | }
25 |
26 | switch f := any(value).(type) {
27 | case float64:
28 | lower = f <= float64(boundary)
29 | case float32:
30 | lower = float64(f) <= float64(boundary)
31 | default:
32 | lower = int64(value) < int64(boundary)
33 | }
34 | return
35 | }
36 |
--------------------------------------------------------------------------------
/utils/platform/cmd.go:
--------------------------------------------------------------------------------
1 | package platform
2 |
3 | import "github.com/ARM-software/golang-utils/utils/subprocess/command"
4 |
5 | // WithPrivileges redefines a command so that it is run with elevated privileges.
6 | // For instance, on Linux, if the current user has enough privileges, the command will be run as is.
7 | // Otherwise, `sudo` will be used if defined as the sudo (See `DefineSudoCommand`).
8 | // Similar scenario will happen on Windows, although the elevated command is defined using `DefineSudoCommand`.
9 | func WithPrivileges(cmd *command.CommandAsDifferentUser) (cmdWithPrivileges *command.CommandAsDifferentUser) {
10 | cmdWithPrivileges = cmd
11 | if cmdWithPrivileges == nil {
12 | cmdWithPrivileges = command.NewCommandAsDifferentUser()
13 | }
14 | hasPrivileges, err := IsCurrentUserAnAdmin()
15 | if err != nil {
16 | return
17 | }
18 | if !hasPrivileges && hasPasswordlessPrivileges() {
19 | cmdWithPrivileges.Prepend(getRunCommandWithPrivileges())
20 | }
21 | return
22 | }
23 |
--------------------------------------------------------------------------------
/utils/logs/logrimp/quiet_logger.go:
--------------------------------------------------------------------------------
1 | package logrimp
2 |
3 | import (
4 | "github.com/go-logr/logr"
5 | )
6 |
7 | type quietLogger struct {
8 | logger logr.Logger
9 | }
10 |
11 | func (l *quietLogger) Init(_ logr.RuntimeInfo) {
12 | // ignored.
13 | }
14 |
15 | func (l *quietLogger) Enabled(int) bool {
16 | return false
17 | }
18 |
19 | func (l *quietLogger) Info(_ int, _ string, _ ...any) {
20 | // Ignored.
21 | }
22 |
23 | func (l *quietLogger) Error(err error, msg string, keysAndValues ...any) {
24 | l.logger.Error(err, msg, keysAndValues...)
25 | }
26 |
27 | func (l *quietLogger) WithValues(keysAndValues ...any) logr.LogSink {
28 | l.logger.WithValues(keysAndValues...)
29 | return l
30 | }
31 |
32 | func (l *quietLogger) WithName(name string) logr.LogSink {
33 | l.logger.WithName(name)
34 | return l
35 | }
36 |
37 | // NewQuietLogger returns a quiet logger which only logs errors.
38 | func NewQuietLogger(logger logr.Logger) logr.Logger {
39 | return logr.New(&quietLogger{logger: logger})
40 | }
41 |
--------------------------------------------------------------------------------
/utils/logs/quiet_logger.go:
--------------------------------------------------------------------------------
1 | package logs
2 |
3 | import "github.com/ARM-software/golang-utils/utils/commonerrors"
4 |
5 | type quietLogger struct {
6 | loggers Loggers
7 | }
8 |
9 | func (l *quietLogger) Close() error {
10 | return l.loggers.Close()
11 | }
12 |
13 | func (l *quietLogger) Check() error {
14 | return l.loggers.Check()
15 | }
16 |
17 | func (l *quietLogger) SetLogSource(source string) error {
18 | return l.loggers.SetLogSource(source)
19 | }
20 |
21 | func (l *quietLogger) SetLoggerSource(source string) error {
22 | return l.loggers.SetLoggerSource(source)
23 | }
24 |
25 | func (l *quietLogger) Log(_ ...interface{}) {
26 | }
27 |
28 | func (l *quietLogger) LogError(err ...interface{}) {
29 | l.loggers.LogError(err...)
30 | }
31 |
32 | // NewQuietLogger returns a quiet logger which only logs errors.
33 | func NewQuietLogger(loggers Loggers) (Loggers, error) {
34 | if loggers == nil {
35 | return nil, commonerrors.ErrNoLogger
36 | }
37 | return &quietLogger{loggers: loggers}, nil
38 | }
39 |
--------------------------------------------------------------------------------
/utils/proc/find/find_other.go:
--------------------------------------------------------------------------------
1 | //go:build !linux
2 |
3 | /*
4 | * Copyright (C) 2020-2024 Arm Limited or its affiliates and Contributors. All rights reserved.
5 | * SPDX-License-Identifier: Apache-2.0
6 | */
7 |
8 | package find
9 |
10 | import (
11 | "context"
12 | "regexp"
13 |
14 | "github.com/ARM-software/golang-utils/utils/collection"
15 | "github.com/ARM-software/golang-utils/utils/parallelisation"
16 | "github.com/ARM-software/golang-utils/utils/proc"
17 | )
18 |
19 | func findProcessByRegex(ctx context.Context, re *regexp.Regexp) (processes []proc.IProcess, err error) {
20 | ps, err := proc.Ps(ctx)
21 | if err != nil || len(ps) == 0 {
22 | return
23 | }
24 |
25 | processes, err = parallelisation.Filter[proc.IProcess](ctx, 10, ps, func(iProcess proc.IProcess) bool {
26 | if iProcess == nil {
27 | return false
28 | }
29 | return collection.AnyTrue(re.MatchString(iProcess.Name()), re.MatchString(iProcess.Executable()), re.MatchString(iProcess.Cmdline()))
30 | })
31 | return
32 | }
33 |
--------------------------------------------------------------------------------
/utils/logs/hclog_logger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/hashicorp/go-hclog"
11 | "github.com/stretchr/testify/require"
12 | "go.uber.org/goleak"
13 |
14 | "github.com/ARM-software/golang-utils/utils/logs/logstest"
15 | )
16 |
17 | func TestHclogLogger(t *testing.T) {
18 | defer goleak.VerifyNone(t)
19 | logger := hclog.New(nil)
20 | loggers, err := NewHclogLogger(logger, "Test")
21 | require.NoError(t, err)
22 | testLog(t, loggers)
23 | }
24 |
25 | func TestHclogWrapper(t *testing.T) {
26 | defer goleak.VerifyNone(t)
27 | loggers, err := NewLogrLogger(logstest.NewTestLogger(t), "test")
28 | require.NoError(t, err)
29 | hcLogger, err := NewHclogWrapper(loggers)
30 | require.NoError(t, err)
31 | loggerTest, err := NewHclogLogger(hcLogger, "Test")
32 | require.NoError(t, err)
33 | testLog(t, loggerTest)
34 | }
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | env:
4 | - GO111MODULE=on
5 | # Commented out because requires Pro features
6 | # before:
7 | # hooks:
8 | # - cmd: go mod tidy
9 | # dir: ./utils
10 | # - cmd: go generate ./...
11 | # dir: ./utils
12 | builds:
13 | - skip: true
14 | archives:
15 | - name_template: >-
16 | {{ .ProjectName }}_
17 | {{- title .Os }}_
18 | {{- if eq .Arch "amd64" }}x86_64
19 | {{- else if eq .Arch "386" }}i386
20 | {{- else }}{{ .Arch }}{{ end }}
21 | sboms:
22 | - artifacts: source
23 | checksum:
24 | name_template: 'checksums.txt'
25 | snapshot:
26 | version_template: "{{ incpatch .Tag }}-dev"
27 | changelog:
28 | sort: asc
29 | filters:
30 | exclude:
31 | - '^docs:'
32 | - '^changes:'
33 | - '^test:'
34 | release:
35 | discussion_category_name: Releases
36 | # If the version tag looks like a pre-release e.g. beta then the GitHub release is set to prerelease
37 | prerelease: auto
38 | name_template: "Release {{.Version}}"
39 |
--------------------------------------------------------------------------------
/utils/strings/strings_test.go:
--------------------------------------------------------------------------------
1 | package strings
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestStrings(t *testing.T) {
11 | // values given by https://rosettacode.org/wiki/Entropy#Groovy
12 | testCases := []struct {
13 | Input string
14 | Entropy float64
15 | }{
16 | {
17 | "1223334444",
18 | 1.846439344671,
19 | },
20 | {
21 | "1223334444555555555",
22 | 1.969811065121,
23 | },
24 | {
25 | "122333",
26 | 1.459147917061,
27 | },
28 | {
29 | "1227774444",
30 | 1.846439344671,
31 | },
32 | {
33 | "aaBBcccDDDD",
34 | 1.936260027482,
35 | },
36 | {
37 | "1234567890abcdefghijklmnopqrstuvwxyz",
38 | 5.169925004424,
39 | },
40 | {
41 | "Rosetta Code",
42 | 3.084962500407,
43 | },
44 | }
45 | for _, testCase := range testCases {
46 | entropy := CalculateStringShannonEntropy(testCase.Input)
47 | require.Equal(t, fmt.Sprintf("%.8f", testCase.Entropy), fmt.Sprintf("%.8f", entropy))
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/utils/filesystem/iofs.go:
--------------------------------------------------------------------------------
1 | package filesystem
2 |
3 | import (
4 | "io/fs"
5 |
6 | "github.com/spf13/afero"
7 |
8 | "github.com/ARM-software/golang-utils/utils/commonerrors"
9 | )
10 |
11 | type wrappedFS struct {
12 | filesystem FS
13 | }
14 |
15 | func (w *wrappedFS) Open(name string) (fs.File, error) {
16 | return w.filesystem.GenericOpen(name)
17 | }
18 |
19 | // ConvertToIOFilesystem converts a filesystem FS to a io/fs FS
20 | func ConvertToIOFilesystem(filesystem FS) (fs.FS, error) {
21 | if filesystem == nil {
22 | return nil, commonerrors.New(commonerrors.ErrUndefined, "missing filesystem")
23 | }
24 | return &wrappedFS{filesystem: filesystem}, nil
25 | }
26 |
27 | // ConvertFromIOFilesystem converts an io/fs FS into a FS
28 | func ConvertFromIOFilesystem(filesystem fs.FS) (FS, error) {
29 | if filesystem == nil {
30 | return nil, commonerrors.New(commonerrors.ErrUndefined, "missing filesystem")
31 | }
32 | return NewVirtualFileSystem(afero.FromIOFS{FS: filesystem}, Custom, IdentityPathConverterFunc), nil
33 | }
34 |
--------------------------------------------------------------------------------
/utils/filesystem/tarfs.go:
--------------------------------------------------------------------------------
1 | package filesystem
2 |
3 | import (
4 | "archive/tar"
5 |
6 | "github.com/spf13/afero"
7 | "github.com/spf13/afero/tarfs"
8 |
9 | "github.com/ARM-software/golang-utils/utils/commonerrors"
10 | )
11 |
12 | func newTarFSAdapterFromReader(reader *tar.Reader) (afero.Fs, error) {
13 | if reader == nil {
14 | return nil, commonerrors.New(commonerrors.ErrUndefined, "missing reader")
15 | }
16 | return afero.NewReadOnlyFs(tarfs.New(reader)), nil
17 | }
18 |
19 | func newTarFSAdapterFromFilePath(fs FS, tarFilePath string, limits ILimits) (tarFs afero.Fs, tarFile File, err error) {
20 | if fs == nil {
21 | err = commonerrors.New(commonerrors.ErrUndefined, "missing filesystem to use for finding the tar file")
22 | return
23 | }
24 | tarReader, tarFile, err := newTarReader(fs, tarFilePath, limits, 0)
25 | if err != nil && tarFile != nil {
26 | subErr := tarFile.Close()
27 | if subErr == nil {
28 | tarFile = nil
29 | }
30 | return
31 | }
32 | tarFs, err = newTarFSAdapterFromReader(tarReader)
33 | return
34 | }
35 |
--------------------------------------------------------------------------------
/utils/sharedcache/config.go:
--------------------------------------------------------------------------------
1 | package sharedcache
2 |
3 | import (
4 | "time"
5 |
6 | validation "github.com/go-ozzo/ozzo-validation/v4"
7 |
8 | configUtils "github.com/ARM-software/golang-utils/utils/config"
9 | )
10 |
11 | type Configuration struct {
12 | RemoteStoragePath string `mapstructure:"remote_storage_path"` // Path where the cache will be stored.
13 | Timeout time.Duration `mapstructure:"timeout"` // Cache timeout if need be
14 | FilesystemItemsToIgnore string `mapstructure:"ignore_fs_items"` // List of files/folders to ignore (pattern list separated by commas)
15 | }
16 |
17 | func (cfg *Configuration) Validate() error {
18 | // Validate Embedded Structs
19 | err := configUtils.ValidateEmbedded(cfg)
20 |
21 | if err != nil {
22 | return err
23 | }
24 |
25 | return validation.ValidateStruct(cfg,
26 | validation.Field(&cfg.RemoteStoragePath, validation.Required),
27 | )
28 | }
29 |
30 | func DefaultSharedCacheConfiguration() *Configuration {
31 | return &Configuration{}
32 | }
33 |
--------------------------------------------------------------------------------
/utils/safecast/number.go:
--------------------------------------------------------------------------------
1 | package safecast
2 |
3 | // This file is highly inspired from https://pkg.go.dev/golang.org/x/exp/constraints
4 |
5 | // ISignedInteger is an alias for all signed integers: int, int8, int16, int32, and int64 types.
6 | type ISignedInteger interface {
7 | ~int | ~int8 | ~int16 | ~int32 | ~int64
8 | }
9 |
10 | // IUnsignedInteger is an alias for all unsigned integers: uint, uint8, uint16, uint32, and uint64 types.
11 | type IUnsignedInteger interface {
12 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
13 | }
14 |
15 | // IInteger is an alias for the all unsigned and signed integers
16 | type IInteger interface {
17 | ISignedInteger | IUnsignedInteger
18 | }
19 |
20 | // IFloat is an alias for the float32 and float64 types.
21 | type IFloat interface {
22 | ~float32 | ~float64
23 | }
24 |
25 | // INumber is an alias for all integers and floats
26 | type INumber interface {
27 | IInteger | IFloat
28 | }
29 |
30 | // IConvertable is an alias for everything that can be converted
31 | type IConvertable interface {
32 | INumber
33 | }
34 |
--------------------------------------------------------------------------------
/utils/filesystem/zipfs.go:
--------------------------------------------------------------------------------
1 | package filesystem
2 |
3 | import (
4 | "archive/zip"
5 | "fmt"
6 |
7 | "github.com/spf13/afero"
8 | "github.com/spf13/afero/zipfs"
9 |
10 | "github.com/ARM-software/golang-utils/utils/commonerrors"
11 | )
12 |
13 | func newZipFSAdapterFromReader(reader *zip.Reader) (afero.Fs, error) {
14 | if reader == nil {
15 | return nil, fmt.Errorf("%w: missing reader", commonerrors.ErrUndefined)
16 | }
17 | return afero.NewReadOnlyFs(zipfs.New(reader)), nil
18 | }
19 |
20 | func newZipFSAdapterFromFilePath(fs FS, zipFilePath string, limits ILimits) (zipFs afero.Fs, zipFile File, err error) {
21 | if fs == nil {
22 | err = fmt.Errorf("%w: missing filesystem to use for finding the zip file", commonerrors.ErrUndefined)
23 | return
24 | }
25 | zipReader, zipFile, err := newZipReader(fs, zipFilePath, limits, 0)
26 | if err != nil && zipFile != nil {
27 | err = ConvertFileSystemError(err)
28 | subErr := zipFile.Close()
29 | if subErr == nil {
30 | zipFile = nil
31 | }
32 | return
33 | }
34 | zipFs, err = newZipFSAdapterFromReader(zipReader)
35 | return
36 | }
37 |
--------------------------------------------------------------------------------
/utils/serialization/slice/slice.go:
--------------------------------------------------------------------------------
1 | package slice
2 |
3 | import (
4 | "github.com/ARM-software/golang-utils/utils/collection"
5 | "github.com/ARM-software/golang-utils/utils/commonerrors"
6 | "github.com/ARM-software/golang-utils/utils/serialization/maps" //nolint:misspell
7 | )
8 |
9 | // ToSlice converts a struct to a list of key values.
10 | func ToSlice[T any](s *T) (list []string, err error) {
11 | r, err := maps.ToMap(s)
12 | if err != nil {
13 | return
14 | }
15 | list = collection.ConvertMapToSlice(r)
16 | return
17 | }
18 |
19 | // FromSlice converts a slice of key,values into a struct i
20 | func FromSlice[T any](s []string, o *T) (err error) {
21 | m, err := collection.ConvertSliceToMap(s)
22 | if err != nil {
23 | err = commonerrors.WrapErrorf(commonerrors.ErrMarshalling, err, "could not convert slice to map so it can be converted into `%T`", o)
24 | return
25 | }
26 | err = maps.FromMap[T](m, o)
27 | return
28 | }
29 |
30 | // FromArgs converts a list of args into a struct o
31 | func FromArgs[T any](o *T, args ...string) error {
32 | return FromSlice(args, o)
33 | }
34 |
--------------------------------------------------------------------------------
/utils/platform/cmd_test.go:
--------------------------------------------------------------------------------
1 | package platform
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/go-faker/faker/v4"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 |
12 | "github.com/ARM-software/golang-utils/utils/subprocess/command"
13 | )
14 |
15 | func TestWithPrivileges(t *testing.T) {
16 | admin, err := IsCurrentUserAnAdmin()
17 | require.NoError(t, err)
18 | name := faker.Username()
19 | cmd := WithPrivileges(command.Gosu(name)).RedefineInShellForm("test", "1", "2", "3")
20 | if admin {
21 | assert.Equal(t, fmt.Sprintf("gosu %v test 1 2 3", name), cmd)
22 | } else {
23 | assert.True(t, strings.Contains(cmd, fmt.Sprintf("gosu %v test 1 2 3", name)))
24 | assert.False(t, strings.HasPrefix(cmd, fmt.Sprintf("gosu %v test 1 2 3", name)))
25 | }
26 | cmd = WithPrivileges(nil).RedefineInShellForm("test", "1", "2", "3")
27 | if admin {
28 | assert.Equal(t, "test 1 2 3", cmd)
29 | } else {
30 | assert.True(t, strings.Contains(cmd, "test 1 2 3"))
31 | assert.False(t, strings.HasPrefix(cmd, "test 1 2 3"))
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/utils/cache/filecache/config.go:
--------------------------------------------------------------------------------
1 | package filecache
2 |
3 | import (
4 | "time"
5 |
6 | validation "github.com/go-ozzo/ozzo-validation/v4"
7 |
8 | configUtils "github.com/ARM-software/golang-utils/utils/config"
9 | )
10 |
11 | type FileCacheConfig struct {
12 | CachePath string `mapstructure:"cache_path"`
13 | GarbageCollectionPeriod time.Duration `mapstructure:"gc_period"`
14 | TTL time.Duration `mapstructure:"ttl"`
15 | }
16 |
17 | func (cfg *FileCacheConfig) Validate() error {
18 | // Validate Embedded Structs
19 | err := configUtils.ValidateEmbedded(cfg)
20 |
21 | if err != nil {
22 | return err
23 | }
24 |
25 | return validation.ValidateStruct(cfg,
26 | validation.Field(&cfg.CachePath, validation.Required),
27 | validation.Field(&cfg.GarbageCollectionPeriod, validation.Required),
28 | validation.Field(&cfg.TTL, validation.Required),
29 | )
30 | }
31 |
32 | func DefaultFileCacheConfig() *FileCacheConfig {
33 | return &FileCacheConfig{
34 | GarbageCollectionPeriod: 10 * time.Minute,
35 | TTL: 2 * time.Hour,
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/utils/serialization/maps/testing/testing.go:
--------------------------------------------------------------------------------
1 | package testing
2 |
3 | import (
4 | "github.com/ARM-software/golang-utils/utils/commonerrors"
5 | )
6 |
7 | type TestEnumWithUnmarshal int
8 |
9 | const (
10 | TestEnumStringVer0 = "test0"
11 | TestEnumStringVer1 = "test1"
12 | )
13 |
14 | func (i *TestEnumWithUnmarshal) UnmarshalText(text []byte) error {
15 | v, ok := map[string]TestEnumWithUnmarshal{
16 | TestEnumStringVer0: TestEnumWithUnmarshal0,
17 | TestEnumStringVer1: TestEnumWithUnmarshal1,
18 | }[string(text)]
19 | if !ok {
20 | return commonerrors.ErrInvalid
21 | }
22 | *i = v
23 | return nil
24 | }
25 |
26 | func ValidationFunc(value any) error {
27 | e, ok := value.(TestEnumWithUnmarshal)
28 | if !ok || (e != TestEnumWithUnmarshal0 && e != TestEnumWithUnmarshal1) {
29 | return commonerrors.ErrInvalid
30 | }
31 | return nil
32 | }
33 |
34 | const (
35 | TestEnumWithUnmarshal0 TestEnumWithUnmarshal = iota
36 | TestEnumWithUnmarshal1
37 | )
38 |
39 | type TestEnumWithoutUnmarshal int
40 |
41 | const (
42 | TestEnumWithoutUnmarshal0 TestEnumWithoutUnmarshal = iota
43 | TestEnumWithoutUnmarshal1
44 | )
45 |
--------------------------------------------------------------------------------
/utils/safeio/write.go:
--------------------------------------------------------------------------------
1 | package safeio
2 |
3 | import (
4 | "context"
5 | "io"
6 |
7 | "github.com/dolmen-go/contextio"
8 | )
9 |
10 | // WriteString writes a string to dst similarly to io.WriteString but with context control to stop when asked to.
11 | func WriteString(ctx context.Context, dst io.Writer, s string) (n int, err error) {
12 | n, err = io.WriteString(ContextualWriter(ctx, dst), s)
13 | err = ConvertIOError(err)
14 | return
15 | }
16 |
17 | // ContextualWriter returns a writer which is context aware.
18 | // Context state is checked BEFORE every Write.
19 | func ContextualWriter(ctx context.Context, writer io.Writer) io.Writer {
20 | return &contextualCopier{w: contextio.NewWriter(ctx, writer)}
21 | }
22 |
23 | type contextualCopier struct {
24 | w io.Writer
25 | }
26 |
27 | func (w *contextualCopier) Write(p []byte) (n int, err error) {
28 | n, err = w.w.Write(p)
29 | err = ConvertIOError(err)
30 | return
31 | }
32 |
33 | func (w *contextualCopier) ReadFrom(r io.Reader) (int64, error) {
34 | if reader, ok := w.w.(io.ReaderFrom); ok {
35 | return safeReadFrom(reader, r)
36 | }
37 | return safeCopy(w.w, r, io.Copy)
38 | }
39 |
--------------------------------------------------------------------------------
/utils/collection/modify.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package collection
6 |
7 | import "slices"
8 |
9 | // Remove looks for elements in a slice. If they're found, it will
10 | // remove them.
11 | func Remove(slice []string, val ...string) []string {
12 | return GenericRemove(func(val1, val2 string) bool { return val1 == val2 }, slice, val...)
13 | }
14 |
15 | // GenericRemove looks for elements in a slice using the equal function. If they're found, it will
16 | // remove them from the slice.
17 | func GenericRemove(equal func(string, string) bool, slice []string, val ...string) []string {
18 | if len(val) == 0 {
19 | return slice
20 | }
21 | if len(val) == 1 {
22 | return slices.DeleteFunc(slice, func(v string) bool { return equal(v, val[0]) })
23 | }
24 | list := make([]string, 0, len(slice))
25 | found := make([]bool, len(val))
26 |
27 | for i := range slice {
28 | e := slice[i]
29 | for j := range val {
30 | found[j] = equal(e, val[j])
31 | }
32 | if !Any(found) {
33 | list = append(list, e)
34 | }
35 | }
36 | return list
37 | }
38 |
--------------------------------------------------------------------------------
/utils/platform/cmd_posix.go:
--------------------------------------------------------------------------------
1 | //go:build linux || unix || (js && wasm) || darwin || aix || dragonfly || freebsd || nacl || netbsd || openbsd || solaris
2 |
3 | package platform
4 |
5 | import (
6 | "os/exec"
7 |
8 | "github.com/ARM-software/golang-utils/utils/subprocess/command"
9 | )
10 |
11 | var (
12 | // sudoCommand describes the command to use to execute command as root
13 | // when running in Docker, change to [gosu root](https://github.com/tianon/gosu)
14 | sudoCommand = command.Sudo()
15 | )
16 |
17 | // DefineSudoCommand defines the command to run to be `root` or a user with enough privileges (superuser).
18 | // e.g.
19 | // - args="sudo" to run commands as `root`
20 | // - args="su", "tom" if `tom` has enough privileges to run the command
21 | func DefineSudoCommand(args ...string) {
22 | sudoCommand = command.NewCommandAsDifferentUser(args...)
23 | }
24 |
25 | func getRunCommandWithPrivileges() *command.CommandAsDifferentUser {
26 | return sudoCommand
27 | }
28 |
29 | func hasPasswordlessPrivileges() bool {
30 | // See https://www.baeldung.com/linux/sudo-passwordless-check
31 | return exec.Command("sh", "-c", "sudo -n true 2>/dev/null || exit 1").Run() == nil
32 | }
33 |
--------------------------------------------------------------------------------
/utils/http/retry_configuration_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package http
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func TestNoRetryConfiguration(t *testing.T) {
14 | configTest := DefaultNoRetryPolicyConfiguration()
15 | require.NoError(t, configTest.Validate())
16 | }
17 |
18 | func TestBasicRetryConfiguration(t *testing.T) {
19 | configTest := DefaultBasicRetryPolicyConfiguration()
20 | require.NoError(t, configTest.Validate())
21 | }
22 |
23 | func TestBasicRetryWithRetryAfterConfiguration(t *testing.T) {
24 | configTest := DefaultRobustRetryPolicyConfiguration()
25 | require.NoError(t, configTest.Validate())
26 | }
27 |
28 | func TestExponentialBackoffRetryConfiguration(t *testing.T) {
29 | configTest := DefaultExponentialBackoffRetryPolicyConfiguration()
30 | require.NoError(t, configTest.Validate())
31 | }
32 |
33 | func TestLinearBackoffRetryConfiguration(t *testing.T) {
34 | configTest := DefaultLinearBackoffRetryPolicyConfiguration()
35 | require.NoError(t, configTest.Validate())
36 | }
37 |
--------------------------------------------------------------------------------
/utils/retry/retry_configuration_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package retry
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func TestNoRetryConfiguration(t *testing.T) {
14 | configTest := DefaultNoRetryPolicyConfiguration()
15 | require.NoError(t, configTest.Validate())
16 | }
17 |
18 | func TestBasicRetryConfiguration(t *testing.T) {
19 | configTest := DefaultBasicRetryPolicyConfiguration()
20 | require.NoError(t, configTest.Validate())
21 | }
22 |
23 | func TestBasicRetryWithRetryAfterConfiguration(t *testing.T) {
24 | configTest := DefaultRobustRetryPolicyConfiguration()
25 | require.NoError(t, configTest.Validate())
26 | }
27 |
28 | func TestExponentialBackoffRetryConfiguration(t *testing.T) {
29 | configTest := DefaultExponentialBackoffRetryPolicyConfiguration()
30 | require.NoError(t, configTest.Validate())
31 | }
32 |
33 | func TestLinearBackoffRetryConfiguration(t *testing.T) {
34 | configTest := DefaultLinearBackoffRetryPolicyConfiguration()
35 | require.NoError(t, configTest.Validate())
36 | }
37 |
--------------------------------------------------------------------------------
/utils/value/empty.go:
--------------------------------------------------------------------------------
1 | package value
2 |
3 | import (
4 | "reflect"
5 | "strings"
6 | )
7 |
8 | // IsEmpty checks whether a value is empty i.e. "", nil, 0, [], {}, false, etc.
9 | // For Strings, a string is considered empty if it is "" or if it only contains whitespaces
10 | func IsEmpty(value any) bool {
11 | if value == nil {
12 | return true
13 | }
14 | if valueStr, ok := value.(string); ok {
15 | return len(strings.TrimSpace(valueStr)) == 0
16 | }
17 | if valueStrPtr, ok := value.(*string); ok {
18 | if valueStrPtr == nil {
19 | return true
20 | }
21 | return len(strings.TrimSpace(*valueStrPtr)) == 0
22 | }
23 | if valueBool, ok := value.(bool); ok {
24 | // if set to true, then value is not empty
25 | return !valueBool
26 | }
27 | objValue := reflect.ValueOf(value)
28 | switch objValue.Kind() {
29 | case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
30 | return objValue.Len() == 0
31 | case reflect.Ptr:
32 | if objValue.IsNil() {
33 | return true
34 | }
35 | deref := objValue.Elem().Interface()
36 | return IsEmpty(deref)
37 | default:
38 | zero := reflect.Zero(objValue.Type())
39 | return reflect.DeepEqual(value, zero.Interface())
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/utils/logs/string_logger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "strings"
9 | "testing"
10 |
11 | "github.com/stretchr/testify/require"
12 | "go.uber.org/goleak"
13 | )
14 |
15 | func TestStringLogger(t *testing.T) {
16 | defer goleak.VerifyNone(t)
17 | loggers, err := NewStringLogger("Test")
18 | require.NoError(t, err)
19 | testLog(t, loggers)
20 | loggers.LogError("Test err")
21 | loggers.Log("Test1")
22 | contents := loggers.GetLogContent()
23 | require.NotEmpty(t, contents)
24 | require.True(t, strings.Contains(contents, "Test err"))
25 | require.True(t, strings.Contains(contents, "Test1"))
26 | }
27 |
28 | func TestPlainStringLogger(t *testing.T) {
29 | defer goleak.VerifyNone(t)
30 | loggers, err := NewPlainStringLogger()
31 | require.NoError(t, err)
32 | testLog(t, loggers)
33 | loggers.LogError("Test err")
34 | loggers.Log("Test1")
35 | contents := loggers.GetLogContent()
36 | require.NotEmpty(t, contents)
37 | require.True(t, strings.Contains(contents, "Test err"))
38 | require.True(t, strings.Contains(contents, "Test1"))
39 | }
40 |
--------------------------------------------------------------------------------
/utils/http/httptest/testing.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package httptest
6 |
7 | import (
8 | "context"
9 | "fmt"
10 | "net"
11 | "net/http"
12 | "testing"
13 | "time"
14 |
15 | "github.com/stretchr/testify/require"
16 |
17 | "github.com/ARM-software/golang-utils/utils/parallelisation"
18 | )
19 |
20 | // NewTestServer creates a test server
21 | func NewTestServer(t *testing.T, ctx context.Context, handler http.Handler, port string) {
22 | t.Helper()
23 | list, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
24 | require.NoError(t, err)
25 | srv := &http.Server{
26 | Handler: handler,
27 | ReadHeaderTimeout: time.Minute,
28 | ReadTimeout: time.Minute,
29 | }
30 | err = parallelisation.DetermineContextError(ctx)
31 | if err != nil {
32 | return
33 | }
34 | // Goroutine in charge of closing the server when requested.
35 | go func(ctx context.Context, server *http.Server) {
36 | <-ctx.Done()
37 | _ = server.Close()
38 | }(ctx, srv)
39 |
40 | go func(server *http.Server, listen net.Listener) {
41 | defer func() { _ = listen.Close() }()
42 | err = srv.Serve(listen)
43 | }(srv, list)
44 | }
45 |
--------------------------------------------------------------------------------
/utils/charset/iconv/interfaces.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | // Package iconv provides utilities to convert characters into different charsets
7 | package iconv
8 |
9 | import (
10 | "context"
11 | "io"
12 | )
13 |
14 | //go:generate go tool mockgen -destination=../../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/charset/$GOPACKAGE ICharsetConverter
15 |
16 | type ICharsetConverter interface {
17 | // ConvertString converts the charset of an input string
18 | ConvertString(input string) (string, error)
19 |
20 | // ConvertStringWithContext converts the charset of an input string
21 | ConvertStringWithContext(ctx context.Context, input string) (string, error)
22 |
23 | // ConvertBytes converts the charset of an input byte array
24 | ConvertBytes(input []byte) ([]byte, error)
25 |
26 | // ConvertBytesWithContext converts the charset of an input byte array
27 | ConvertBytesWithContext(ctx context.Context, input []byte) ([]byte, error)
28 |
29 | // Convert converts the charset of a reader
30 | Convert(reader io.Reader) io.Reader
31 |
32 | // String describes the conversion
33 | String() string
34 | }
35 |
--------------------------------------------------------------------------------
/utils/config/validation_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func Test_processMapStructureString(t *testing.T) {
10 | tests := []struct {
11 | mapstructureTag string
12 | expectedProcessedTag string
13 | }{
14 | {},
15 | {
16 | mapstructureTag: " ",
17 | },
18 | {
19 | mapstructureTag: " - ",
20 | },
21 | {
22 | mapstructureTag: " , omitzero ",
23 | },
24 | {
25 | mapstructureTag: " ,omitempty , omitzero , SQUASH ",
26 | },
27 | {
28 | mapstructureTag: "test ,omitempty , omitzero , squash ",
29 | expectedProcessedTag: "test",
30 | },
31 | {
32 | mapstructureTag: "person_name",
33 | expectedProcessedTag: "person_name",
34 | },
35 | {
36 | mapstructureTag: " person_name ",
37 | expectedProcessedTag: "person_name",
38 | },
39 | {
40 | mapstructureTag: " person_name ,remain ",
41 | expectedProcessedTag: "person_name",
42 | },
43 | }
44 |
45 | for i := range tests {
46 | test := tests[i]
47 | t.Run(test.mapstructureTag, func(t *testing.T) {
48 | assert.Equal(t, test.expectedProcessedTag, processMapStructureString(test.mapstructureTag))
49 | })
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Release
2 |
3 | env:
4 | go_version: "1.24"
5 |
6 | on:
7 | workflow_dispatch:
8 | inputs:
9 | release_type:
10 | description:
11 | "Enter type of release to perform (i.e. development, beta, release):"
12 | required: true
13 | jobs:
14 | release:
15 | name: Carry out a release
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v4
19 | with:
20 | # Get the full history as this is required by goreleaser
21 | fetch-depth: 0
22 | - name: Set up Go
23 | uses: actions/setup-go@v5
24 | with:
25 | go-version: ${{ env.go_version }}
26 | - uses: actions/setup-python@v5
27 | with:
28 | python-version: '3.11'
29 | - uses: FranzDiebold/github-env-vars-action@v2
30 | - name: Install CI/CD tools
31 | run: pip install continuous-delivery-scripts>=2.7 && pip list
32 | - name: Tag and release
33 | run: cd-tag-and-release -b ${CI_ACTION_REF_NAME} -t ${{ github.event.inputs.release_type }} -vv
34 | env:
35 | # Using a specific token because GITHUB_TOKEN is not available https://github.com/marketplace/actions/workflow-dispatch#token
36 | GIT_TOKEN: ${{ secrets.GIT_SECRET }}
37 |
--------------------------------------------------------------------------------
/utils/logs/writer_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "log"
9 | "os"
10 | "testing"
11 | "time"
12 |
13 | "github.com/stretchr/testify/require"
14 | "go.uber.org/goleak"
15 | )
16 |
17 | type SlowWriter struct {
18 | StdWriter
19 | }
20 |
21 | func (w *SlowWriter) Write(p []byte) (n int, err error) {
22 | time.Sleep(10 * time.Millisecond)
23 | return os.Stdout.Write(p)
24 | }
25 |
26 | func NewTestSlowWriter(t *testing.T) *SlowWriter {
27 | t.Helper()
28 | return &SlowWriter{}
29 | }
30 |
31 | // Creates a logger to standard output/error
32 | func NewTestMultipleWriterLogger(t *testing.T, prefix string) (loggers Loggers) {
33 | t.Helper()
34 | writer, err := NewMultipleWritersWithSource(NewTestSlowWriter(t), NewTestSlowWriter(t))
35 | require.NoError(t, err)
36 | loggers = &GenericLoggers{
37 | Output: log.New(writer, "["+prefix+"] Output: ", log.LstdFlags),
38 | Error: log.New(writer, "["+prefix+"] Error: ", log.LstdFlags),
39 | }
40 | return
41 | }
42 |
43 | func TestMultipleWriters(t *testing.T) {
44 | defer goleak.VerifyNone(t)
45 | testLog(t, NewTestMultipleWriterLogger(t, "Test"))
46 | time.Sleep(100 * time.Millisecond)
47 | }
48 |
--------------------------------------------------------------------------------
/utils/platform/homedir.go:
--------------------------------------------------------------------------------
1 | package platform
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/mitchellh/go-homedir"
7 |
8 | "github.com/ARM-software/golang-utils/utils/commonerrors"
9 | )
10 |
11 | // GetHomeDirectory returns the home directory of a user.
12 | func GetHomeDirectory(username string) (string, error) {
13 | user, err := GetUser(username)
14 | if err != nil {
15 | return GetDefaultHomeDirectory(username)
16 | }
17 | return fetchHomeDirectory(username, user.HomeDir)
18 | }
19 |
20 | // GetDefaultHomeDirectory returns the default home directory for a user based on the convention listed in https://en.wikipedia.org/wiki/Home_directory#Default_home_directory_per_operating_system
21 | func GetDefaultHomeDirectory(username string) (string, error) {
22 | if username == "" {
23 | return "", fmt.Errorf("%w: missing username", commonerrors.ErrUndefined)
24 | }
25 | return determineDefaultHomeDirectory(username)
26 | }
27 |
28 | func fetchHomeDirectory(username, homedirPath string) (home string, err error) {
29 | home, err = homedir.Expand(homedirPath)
30 | if err == nil {
31 | return
32 | }
33 | home, err = GetDefaultHomeDirectory(username)
34 | if err != nil {
35 | err = fmt.Errorf("%w: could not determine user [%v]'s home directory: %v", commonerrors.ErrUnexpected, username, err.Error())
36 | }
37 | return
38 | }
39 |
--------------------------------------------------------------------------------
/utils/logs/zap_logger.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | // Package logs defines loggers for use in projects.
7 | package logs
8 |
9 | import (
10 | "go.uber.org/zap"
11 |
12 | "github.com/ARM-software/golang-utils/utils/commonerrors"
13 | "github.com/ARM-software/golang-utils/utils/logs/logrimp"
14 | )
15 |
16 | const (
17 | syncError = "invalid argument" // sync error can happen on Linux (sync /dev/stderr: invalid argument) see https://github.com/uber-go/zap/issues/328
18 | syncErrorMac = "bad file descriptor" // sync error can happen on MacOs (sync /dev/stderr: invalid argument) see https://github.com/uber-go/zap/issues/328
19 |
20 | )
21 |
22 | // NewZapLogger returns a logger which uses zap logger (https://github.com/uber-go/zap)
23 | func NewZapLogger(zapL *zap.Logger, loggerSource string) (loggers Loggers, err error) {
24 | if zapL == nil {
25 | err = commonerrors.ErrNoLogger
26 | return
27 | }
28 | return NewLogrLoggerWithClose(logrimp.NewZapLogger(zapL), loggerSource, func() error {
29 | err := zapL.Sync()
30 | // handling this error https://github.com/uber-go/zap/issues/328
31 | if commonerrors.CorrespondTo(err, syncError, syncErrorMac) {
32 | return nil
33 | }
34 | return err
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | checks:
3 | argument-count:
4 | enabled: true
5 | config:
6 | threshold: 8
7 | complex-logic:
8 | enabled: true
9 | config:
10 | threshold: 4
11 | file-lines:
12 | enabled: true
13 | config:
14 | threshold: 1000
15 | method-complexity:
16 | enabled: true
17 | config:
18 | threshold: 5
19 | method-count:
20 | enabled: true
21 | config:
22 | threshold: 100
23 | method-lines:
24 | enabled: true
25 | config:
26 | threshold: 50
27 | nested-control-flow:
28 | enabled: true
29 | config:
30 | threshold: 4
31 | return-statements:
32 | enabled: true
33 | config:
34 | threshold: 20
35 | similar-code:
36 | enabled: true
37 | config:
38 | threshold: #language-specific defaults. overrides affect all languages.
39 | identical-code:
40 | enabled: true
41 | config:
42 | threshold: #language-specific defaults. overrides affect all languages.
43 | plugins:
44 | rubocop:
45 | enabled: true
46 | eslint:
47 | enabled: true
48 | exclude_patterns:
49 | - "config/"
50 | - "db/"
51 | - "dist/"
52 | - "features/"
53 | - "docs/"
54 | - "changes/"
55 | - "**/node_modules/"
56 | - "script/"
57 | - "**/spec/"
58 | - "**/mock/"
59 | - "**/mocks/"
60 | - "**/test/"
61 | - "**/tests/"
62 | - "**/vendor/"
63 | - "**/*.d.ts"
64 | - "**/*_test.go"
65 |
--------------------------------------------------------------------------------
/utils/http/configuration_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package http
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func TestHttpClientConfiguration(t *testing.T) {
14 | configTest := DefaultHTTPClientConfiguration()
15 | require.NoError(t, configTest.Validate())
16 | }
17 |
18 | func TestFastHttpClientConfiguration(t *testing.T) {
19 | configTest := FastHTTPClientConfiguration()
20 | require.NoError(t, configTest.Validate())
21 | }
22 |
23 | func TestHttpClientConfigurationWithRetry(t *testing.T) {
24 | configTest := DefaultRobustHTTPClientConfiguration()
25 | require.NoError(t, configTest.Validate())
26 | }
27 |
28 | func TestHttpClientConfigurationWithRetryAndRetryAfter(t *testing.T) {
29 | configTest := DefaultRobustHTTPClientConfigurationWithRetryAfter()
30 | require.NoError(t, configTest.Validate())
31 | }
32 |
33 | func TestHttpClientConfigurationWithBackoff(t *testing.T) {
34 | configTest := DefaultRobustHTTPClientConfigurationWithExponentialBackOff()
35 | require.NoError(t, configTest.Validate())
36 | }
37 |
38 | func TestHttpClientConfigurationWithLinearBackoff(t *testing.T) {
39 | configTest := DefaultRobustHTTPClientConfigurationWithLinearBackOff()
40 | require.NoError(t, configTest.Validate())
41 | }
42 |
--------------------------------------------------------------------------------
/utils/collection/range.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "iter"
5 |
6 | "github.com/ARM-software/golang-utils/utils/field"
7 | )
8 |
9 | func sign(x int) int {
10 | if x < 0 {
11 | return -1
12 | }
13 | return 1
14 | }
15 |
16 | // Range returns a slice of integers similar to Python's built-in range().
17 | // https://docs.python.org/2/library/functions.html#range
18 | //
19 | // Note: The stop value is always exclusive.
20 | func Range(start, stop int, step *int) (result []int) {
21 | it, length := rangeSequence(start, stop, step)
22 | result = make([]int, length)
23 | i := 0
24 | for v := range it {
25 | result[i] = v
26 | i++
27 | }
28 | return result
29 | }
30 |
31 | // RangeSequence returns an iterator over a range
32 | func RangeSequence(start, stop int, step *int) iter.Seq[int] {
33 | it, _ := rangeSequence(start, stop, step)
34 | return it
35 | }
36 |
37 | func rangeSequence(start, stop int, step *int) (it iter.Seq[int], length int) {
38 | s := field.OptionalInt(step, 1)
39 | if s == 0 {
40 | it = func(yield func(int) bool) {}
41 | return
42 | }
43 | // Compute length
44 | if (s > 0 && start < stop) || (s < 0 && start > stop) {
45 | length = (stop - start + s - sign(s)) / s
46 | }
47 | it = func(yield func(int) (end bool)) {
48 | for i, v := 0, start; i < length; i, v = i+1, v+s {
49 | if !yield(v) {
50 | return
51 | }
52 | }
53 | }
54 | return
55 | }
56 |
--------------------------------------------------------------------------------
/utils/platform/users_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package platform
4 |
5 | import (
6 | "os"
7 | "os/exec"
8 | "os/user"
9 |
10 | "golang.org/x/sys/windows"
11 |
12 | "github.com/ARM-software/golang-utils/utils/collection"
13 | )
14 |
15 | const (
16 | AdministratorsGroup = "S-1-5-32-544" //https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers
17 | )
18 |
19 | func isUserAdmin(user *user.User) (admin bool, err error) {
20 | gids, subErr := user.GroupIds()
21 | if subErr == nil {
22 | _, admin = collection.FindInSlice(true, gids, AdministratorsGroup)
23 | if admin {
24 | return
25 | }
26 | }
27 | admin, err = isAdmin(user.Username)
28 | return
29 | }
30 |
31 | func isCurrentAdmin() (admin bool, err error) {
32 | // In order to avoid, this https://stackoverflow.com/questions/8046097/how-to-check-if-a-process-has-the-administrative-rights
33 | // https://gist.github.com/jerblack/d0eb182cc5a1c1d92d92a4c4fcc416c6
34 | file, subErr := os.Open("\\\\.\\PHYSICALDRIVE0")
35 | defer func() {
36 | if file != nil {
37 | _ = file.Close()
38 | }
39 | }()
40 | admin = subErr == nil
41 | if admin {
42 | return
43 | }
44 | // extra check https://stackoverflow.com/a/28268802
45 | subErr = exec.Command("fltmc").Run()
46 | admin = subErr == nil
47 | if admin {
48 | return
49 | }
50 | admin = windows.GetCurrentProcessToken().IsElevated()
51 | return
52 | }
53 |
--------------------------------------------------------------------------------
/utils/parallelisation/wait.go:
--------------------------------------------------------------------------------
1 | package parallelisation
2 |
3 | import (
4 | "context"
5 |
6 | "golang.org/x/sync/errgroup"
7 | )
8 |
9 | // IErrorWaiter can be used to wait on errgroups and similar types where Wait() returns an error
10 | // This is used to support use in the WaitWithContextAndError function to wait but listen to contexts
11 | type IErrorWaiter interface {
12 | Wait() error
13 | }
14 |
15 | func WaitWithContextAndError(ctx context.Context, wg IErrorWaiter) (err error) {
16 | done := make(chan struct{})
17 | var g errgroup.Group
18 | g.SetLimit(1)
19 | g.Go(func() error {
20 | defer close(done)
21 | return wg.Wait()
22 | })
23 | select {
24 | case <-ctx.Done():
25 | return DetermineContextError(ctx)
26 | case <-done:
27 | return g.Wait() // since there is only one this will return when wg does
28 | }
29 | }
30 |
31 | // IWaiter can be used to wait on sync WaitGroups and similar types where Wait() does not return an error
32 | // This is used to support use in the WaitWithContext function to wait but listen to contexts
33 | type IWaiter interface {
34 | Wait()
35 | }
36 |
37 | func WaitWithContext(ctx context.Context, wg IWaiter) (err error) {
38 | done := make(chan struct{})
39 | var g errgroup.Group
40 | g.SetLimit(1)
41 | go func() {
42 | defer close(done)
43 | wg.Wait()
44 | }()
45 | select {
46 | case <-ctx.Done():
47 | return DetermineContextError(ctx)
48 | case <-done:
49 | return nil
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/utils/git/interfaces.go:
--------------------------------------------------------------------------------
1 | package git
2 |
3 | import (
4 | "github.com/go-git/go-git/v5"
5 | "github.com/go-git/go-git/v5/plumbing/transport"
6 |
7 | "github.com/ARM-software/golang-utils/utils/config"
8 | )
9 |
10 | // ILimits defines general GitLimits for actions performed during a git clone
11 | type ILimits interface {
12 | config.IServiceConfiguration
13 | // Apply states whether the limit should be applied
14 | Apply() bool
15 | // GetMaxFileSize returns the maximum size in byte a file can have on a file system
16 | GetMaxFileSize() int64
17 | // GetMaxTotalSize returns the maximum size in byte a location can have on a file system (whether it is a file or a folder)
18 | GetMaxTotalSize() int64
19 | // GetMaxFileCount returns the maximum number of files allowed in a reposittory
20 | GetMaxFileCount() int64
21 | // GetMaxTreeDepth returns the maximum tree depth for a repository
22 | GetMaxTreeDepth() int64
23 | // GetMaxEntries returns the maximum total entries allowed in the it repo
24 | GetMaxEntries() int64
25 | // GetMaxTrueSize returns the maximum true size of the repo based on blobs
26 | GetMaxTrueSize() int64
27 | }
28 |
29 | type IGitActionConfig interface {
30 | config.IServiceConfiguration
31 | GetUrl() string
32 | GetAuth() transport.AuthMethod
33 | GetDepth() int
34 | GetReference() string
35 | GetRecursiveSubModules() git.SubmoduleRescursivity
36 | GetTags() git.TagMode
37 | GetCreate() bool
38 | GetNoCheckout() bool
39 | }
40 |
--------------------------------------------------------------------------------
/utils/units/size/sizetest/units_test.go:
--------------------------------------------------------------------------------
1 | package sizetest
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 |
9 | "github.com/ARM-software/golang-utils/utils/filesystem"
10 | "github.com/ARM-software/golang-utils/utils/units/size"
11 | )
12 |
13 | // If it works for M and K it will most likely work for the larger ones as they are just multiplying by 1000/1024
14 |
15 | var testCases = []float64{
16 | 1 * size.B, 12 * size.B, 125 * size.B, 988 * size.B, 67 * size.B,
17 | 1 * size.KB, 102 * size.KB, 12.5 * size.KB, 98 * size.KB, 679 * size.KB,
18 | 1 * size.MB, 77 * size.MB, 5 * size.MB, 188 * size.MB, 617 * size.MB,
19 | 1011 * size.KiB, 2 * size.KiB, 1000 * size.KiB, 2000 * size.KiB, 1111 * size.KiB,
20 | 27 * size.MiB, 2 * size.MiB, 76 * size.MiB, 22 * size.MiB, 0.7 * size.MiB,
21 | }
22 |
23 | func TestGetFileSize(t *testing.T) {
24 | fs := filesystem.NewFs(filesystem.InMemoryFS)
25 |
26 | for i := range testCases {
27 | tmpFile, err := fs.TempFileInTempDir("test-filesize-")
28 | require.NoError(t, err)
29 | data := make([]byte, int64(testCases[i]))
30 | _, _ = tmpFile.Write(data)
31 |
32 | err = tmpFile.Close()
33 | require.NoError(t, err)
34 |
35 | fileName := tmpFile.Name()
36 | defer func() { _ = fs.Rm(fileName) }()
37 |
38 | size, err := fs.GetFileSize(tmpFile.Name())
39 | require.NoError(t, err)
40 | assert.Equal(t, int64(testCases[i]), size)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/utils/maps/overall_test.go:
--------------------------------------------------------------------------------
1 | package maps
2 |
3 | import (
4 | "strconv"
5 | "testing"
6 | "time"
7 |
8 | "github.com/go-faker/faker/v4"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func TestMapFlattenExpand(t *testing.T) {
14 | for i := 0; i < 10; i++ {
15 | t.Run(strconv.Itoa(i), func(t *testing.T) {
16 | testCase := map[string]any{
17 | "test1": map[string]any{
18 | "test1.1": faker.DomainName(),
19 | "test2": map[string]any{
20 | faker.Name(): faker.Paragraph(),
21 | "test3": map[string]any{
22 | faker.Word(): faker.Phonenumber(),
23 | "test3.1": 5.54,
24 | "some time": time.Now().UTC(),
25 | "some float": 45454.454545812,
26 | faker.UUIDDigit(): faker.RandomUnixTime(),
27 | faker.Password(): time.Now().UTC(),
28 | "test4": map[string]any{
29 | "test5": map[string]time.Duration{
30 | faker.DomainName(): 5 * time.Hour,
31 | },
32 | },
33 | },
34 | },
35 | },
36 | }
37 |
38 | flattened, err := Flatten(testCase)
39 | require.NoError(t, err)
40 | expanded, err := Expand(flattened)
41 | require.NoError(t, err)
42 | flattened, err = Flatten(testCase)
43 | require.NoError(t, err)
44 | expanded2, err := Expand(flattened)
45 | require.NoError(t, err)
46 | assert.NotEmpty(t, expanded)
47 | assert.Equal(t, expanded, expanded2)
48 | })
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/.golangci.yaml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | run:
3 | build-tags:
4 | - integration
5 | linters:
6 | default: none
7 | enable:
8 | - bodyclose
9 | - errcheck
10 | - gocritic
11 | - gosec
12 | - govet
13 | - ineffassign
14 | - misspell
15 | - revive
16 | - staticcheck
17 | - unconvert
18 | - unused
19 | settings:
20 | misspell:
21 | locale: UK
22 | extra-words:
23 | - typo: sanetisation
24 | correction: sanitisation
25 | - typo: sanetise
26 | correction: sanitise
27 | - typo: sanetising
28 | correction: sanitising
29 | revive:
30 | rules:
31 | - name: exported
32 | arguments:
33 | - disableStutteringCheck
34 | severity: warning
35 | disabled: false
36 | exclusions:
37 | generated: lax
38 | presets:
39 | - comments
40 | - common-false-positives
41 | - legacy
42 | - std-error-handling
43 | paths:
44 | - third_party$
45 | - builtin$
46 | - examples$
47 | formatters:
48 | enable:
49 | - gci
50 | - goimports
51 | settings:
52 | gci:
53 | sections:
54 | - Standard
55 | - Default
56 | - Prefix(github.com/ARM-software)
57 | goimports:
58 | local-prefixes:
59 | - github.com/ARM-software
60 | exclusions:
61 | generated: lax
62 | paths:
63 | - third_party$
64 | - builtin$
65 | - examples$
66 |
--------------------------------------------------------------------------------
/utils/filesystem/tar.go:
--------------------------------------------------------------------------------
1 | package filesystem
2 |
3 | import (
4 | "archive/tar"
5 |
6 | "github.com/ARM-software/golang-utils/utils/commonerrors"
7 | )
8 |
9 | func newTarReader(fs FS, source string, limits ILimits, currentDepth int64) (tarReader *tar.Reader, file File, err error) {
10 | if fs == nil {
11 | err = commonerrors.New(commonerrors.ErrUndefined, "missing file system")
12 | return
13 | }
14 | if limits == nil {
15 | err = commonerrors.New(commonerrors.ErrUndefined, "missing file system limits")
16 | return
17 | }
18 | if limits.Apply() && limits.GetMaxDepth() >= 0 && currentDepth > limits.GetMaxDepth() {
19 | err = commonerrors.Newf(commonerrors.ErrTooLarge, "depth [%v] of tar file [%v] is beyond allowed limits (max: %v)", currentDepth, source, limits.GetMaxDepth())
20 | return
21 | }
22 |
23 | if !fs.Exists(source) {
24 | err = commonerrors.Newf(commonerrors.ErrNotFound, "could not find archive [%v]", source)
25 | return
26 | }
27 |
28 | info, err := fs.Lstat(source)
29 | if err != nil {
30 | return
31 | }
32 | file, err = fs.GenericOpen(source)
33 | if err != nil {
34 | return
35 | }
36 |
37 | tarFileSize := info.Size()
38 |
39 | if limits.Apply() && tarFileSize > limits.GetMaxFileSize() {
40 | err = commonerrors.Newf(commonerrors.ErrTooLarge, "tar file [%v] is too big (%v B) and beyond limits (max: %v B)", source, tarFileSize, limits.GetMaxFileSize())
41 | return
42 | }
43 |
44 | tarReader = tar.NewReader(file)
45 |
46 | return
47 | }
48 |
--------------------------------------------------------------------------------
/utils/transaction/saga/types.go:
--------------------------------------------------------------------------------
1 | package saga
2 |
3 | import (
4 | "crypto/rand"
5 | "fmt"
6 |
7 | "github.com/ARM-software/golang-utils/utils/reflection"
8 | )
9 |
10 | type stepIdentifier struct {
11 | name string
12 | namespace string
13 | }
14 |
15 | func (i *stepIdentifier) String() string {
16 | return fmt.Sprintf("%s@%s", i.name, i.namespace)
17 | }
18 |
19 | func (i *stepIdentifier) GetName() string {
20 | return i.name
21 | }
22 |
23 | func (i *stepIdentifier) GetNamespace() string {
24 | return i.namespace
25 | }
26 |
27 | func NewStepIdentifier(name, namespace string) IActionIdentifier {
28 | return &stepIdentifier{
29 | name: name,
30 | namespace: namespace,
31 | }
32 | }
33 |
34 | type stepArguments struct {
35 | idemKey string
36 | args map[string]any
37 | }
38 |
39 | func (a *stepArguments) GetIdemKey() string {
40 | return a.idemKey
41 | }
42 |
43 | func (a *stepArguments) GetArguments() map[string]any {
44 | return a.args
45 | }
46 |
47 | func NewStepArgumentsWithIdempotentKey(idemKey string, args map[string]any) IActionArguments {
48 | m := args
49 | if reflection.IsEmpty(m) {
50 | m = map[string]any{}
51 | }
52 | return &stepArguments{
53 | idemKey: idemKey,
54 | args: m,
55 | }
56 | }
57 |
58 | func NewStepArguments(args map[string]any) IActionArguments {
59 | return NewStepArgumentsWithIdempotentKey(rand.Text(), args) //nolint:govet
60 | }
61 |
62 | func NoStepArguments() IActionArguments {
63 | return NewStepArguments(nil)
64 | }
65 |
--------------------------------------------------------------------------------
/utils/logs/logrimp/quiet_logger_test.go:
--------------------------------------------------------------------------------
1 | package logrimp
2 |
3 | import (
4 | "log/slog"
5 | "os"
6 | "testing"
7 |
8 | "github.com/go-faker/faker/v4"
9 | "github.com/go-logr/logr"
10 | "github.com/hashicorp/go-hclog"
11 | "github.com/sirupsen/logrus"
12 | "github.com/stretchr/testify/require"
13 | "go.uber.org/zap"
14 |
15 | "github.com/ARM-software/golang-utils/utils/commonerrors"
16 | )
17 |
18 | func TestQuietLoggerImplementations(t *testing.T) {
19 | zl, err := zap.NewDevelopment()
20 | require.NoError(t, err)
21 | tests := []struct {
22 | Logger logr.Logger
23 | name string
24 | }{
25 | {
26 | Logger: NewNoopLogger(),
27 | name: "NoOp",
28 | },
29 | {
30 | Logger: NewStdOutLogr(),
31 | name: "Standard Output",
32 | },
33 | {
34 | Logger: NewZapLogger(zl),
35 | name: "Zap",
36 | },
37 | {
38 | Logger: NewHclogLogger(hclog.New(nil)),
39 | name: "HClog",
40 | },
41 | {
42 | Logger: NewLogrusLogger(logrus.New()),
43 | name: "Logrus",
44 | },
45 | {
46 | Logger: NewSlogLogger(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{}))),
47 | name: "slog",
48 | },
49 | }
50 | for i := range tests {
51 | test := tests[i]
52 | t.Run(test.name, func(t *testing.T) {
53 | logger := NewQuietLogger(test.Logger)
54 | logger.WithName(faker.Name()).WithValues("foo", "bar").Info(faker.Sentence())
55 | logger.Error(commonerrors.ErrUnexpected, faker.Sentence(), faker.Word(), faker.Name())
56 | },
57 | )
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/utils/proc/ps_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | /*
4 | * Copyright (C) 2020-2024 Arm Limited or its affiliates and Contributors. All rights reserved.
5 | * SPDX-License-Identifier: Apache-2.0
6 | */
7 |
8 | package proc
9 |
10 | import (
11 | "context"
12 | "os/exec"
13 | "strconv"
14 | "time"
15 |
16 | "github.com/ARM-software/golang-utils/utils/commonerrors"
17 | "github.com/ARM-software/golang-utils/utils/parallelisation"
18 | )
19 |
20 | func killGroup(ctx context.Context, pid int32) (err error) {
21 | err = parallelisation.DetermineContextError(ctx)
22 | if err != nil {
23 | return
24 | }
25 | cmd := exec.CommandContext(ctx, "taskkill", "/f", "/t", "/pid", strconv.Itoa(int(pid)))
26 | // setting the following to avoid having hanging subprocesses as described in https://github.com/golang/go/issues/24050
27 | cmd.WaitDelay = 50 * time.Millisecond
28 | err = ConvertProcessError(cmd.Run())
29 | if err != nil {
30 | err = commonerrors.WrapErrorf(commonerrors.ErrUnexpected, err, "could not kill process group (#%v)", pid)
31 | }
32 | return
33 | }
34 |
35 | func getGroupProcesses(ctx context.Context, pid int) (pids []int, err error) {
36 | parent, err := FindProcess(ctx, pid)
37 | if err != nil {
38 | return
39 | }
40 | children, err := parent.Children(ctx)
41 | if err != nil {
42 | return
43 | }
44 | // Windows doesn't have group PIDs
45 | pids = make([]int, len(children)+1)
46 | pids[0] = parent.Pid()
47 | for i := range children {
48 | pids[i+1] = children[i].Pid()
49 | }
50 | return
51 | }
52 |
--------------------------------------------------------------------------------
/utils/http/headers/useragent/useragent_test.go:
--------------------------------------------------------------------------------
1 | package useragent
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/go-faker/faker/v4"
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 |
10 | "github.com/ARM-software/golang-utils/utils/commonerrors"
11 | "github.com/ARM-software/golang-utils/utils/commonerrors/errortest"
12 | )
13 |
14 | func TestAddValuesToUserAgent(t *testing.T) {
15 | userAgent, err := GenerateUserAgentValue(faker.Name(), faker.Word(), faker.Sentence())
16 | require.NoError(t, err)
17 | newAgent := AddValuesToUserAgent(userAgent, faker.Word())
18 | assert.Contains(t, newAgent, userAgent)
19 | elem := faker.Word()
20 | newAgent = AddValuesToUserAgent(" ", elem)
21 | assert.Equal(t, elem, newAgent)
22 | }
23 |
24 | func TestGenerateUserAgentValue(t *testing.T) {
25 | userAgent, err := GenerateUserAgentValue("", "", "")
26 | errortest.AssertError(t, err, commonerrors.ErrUndefined, commonerrors.ErrInvalid)
27 | assert.Empty(t, userAgent)
28 | userAgent, err = GenerateUserAgentValue(" ", " ", faker.Sentence())
29 | errortest.AssertError(t, err, commonerrors.ErrUndefined, commonerrors.ErrInvalid)
30 | assert.Empty(t, userAgent)
31 | userAgent, err = GenerateUserAgentValue(faker.Name(), "", faker.Sentence())
32 | errortest.AssertError(t, err, commonerrors.ErrUndefined, commonerrors.ErrInvalid)
33 | assert.Empty(t, userAgent)
34 | userAgent, err = GenerateUserAgentValue(faker.Name(), faker.Word(), faker.Sentence())
35 | assert.NoError(t, err)
36 | assert.NotEmpty(t, userAgent)
37 | }
38 |
--------------------------------------------------------------------------------
/utils/logs/logrimp/logr_test.go:
--------------------------------------------------------------------------------
1 | package logrimp
2 |
3 | import (
4 | "log/slog"
5 | "os"
6 | "testing"
7 |
8 | "github.com/go-faker/faker/v4"
9 | "github.com/go-logr/logr"
10 | "github.com/hashicorp/go-hclog"
11 | "github.com/sirupsen/logrus"
12 | "github.com/stretchr/testify/require"
13 | "go.uber.org/goleak"
14 | "go.uber.org/zap"
15 |
16 | "github.com/ARM-software/golang-utils/utils/commonerrors"
17 | )
18 |
19 | func TestLoggerImplementations(t *testing.T) {
20 | defer goleak.VerifyNone(t)
21 | zl, err := zap.NewDevelopment()
22 | require.NoError(t, err)
23 | tests := []struct {
24 | Logger logr.Logger
25 | name string
26 | }{
27 | {
28 | Logger: NewNoopLogger(),
29 | name: "NoOp",
30 | },
31 | {
32 | Logger: NewStdOutLogr(),
33 | name: "Standard Output",
34 | },
35 | {
36 | Logger: NewZapLogger(zl),
37 | name: "Zap",
38 | },
39 | {
40 | Logger: NewHclogLogger(hclog.New(nil)),
41 | name: "HClog",
42 | },
43 | {
44 | Logger: NewLogrusLogger(logrus.New()),
45 | name: "Logrus",
46 | },
47 | {
48 | Logger: NewSlogLogger(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{}))),
49 | name: "slog",
50 | },
51 | }
52 | for i := range tests {
53 | test := tests[i]
54 | t.Run(test.name, func(t *testing.T) {
55 | defer goleak.VerifyNone(t)
56 | logger := test.Logger
57 | logger.WithName(faker.Name()).WithValues("foo", "bar").Info(faker.Sentence())
58 | logger.Error(commonerrors.ErrUnexpected, faker.Sentence(), faker.Word(), faker.Name())
59 | },
60 | )
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/utils/subprocess/logging.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package subprocess
6 |
7 | import (
8 | "context"
9 | "io"
10 | "strings"
11 |
12 | "github.com/ARM-software/golang-utils/utils/logs"
13 | "github.com/ARM-software/golang-utils/utils/platform"
14 | "github.com/ARM-software/golang-utils/utils/safeio"
15 | )
16 |
17 | var lineSep = platform.UnixLineSeparator()
18 |
19 | // INTERNAL
20 | // Way of redirecting process output to a logger.
21 | type logStreamer struct {
22 | IsStdErr bool
23 | Loggers logs.Loggers
24 | }
25 |
26 | func (l *logStreamer) Write(p []byte) (n int, err error) {
27 | lines := strings.Split(string(p), lineSep)
28 | for i := range lines { // https://stackoverflow.com/questions/62446118/implicit-memory-aliasing-in-for-loop
29 | line := lines[i]
30 | if line != "" {
31 | if l.IsStdErr {
32 | l.Loggers.LogError(line)
33 | } else {
34 | l.Loggers.Log(line)
35 | }
36 | }
37 | }
38 | return len(p), nil
39 | }
40 |
41 | func newLogStreamer(ctx context.Context, isStdErr bool, loggers logs.Loggers) io.Writer {
42 | return safeio.ContextualWriter(ctx, &logStreamer{
43 | IsStdErr: isStdErr,
44 | Loggers: loggers,
45 | })
46 | }
47 |
48 | func newOutStreamer(ctx context.Context, loggers logs.Loggers) io.Writer {
49 | return newLogStreamer(ctx, false, loggers)
50 | }
51 |
52 | func newErrLogStreamer(ctx context.Context, loggers logs.Loggers) io.Writer {
53 | return newLogStreamer(ctx, true, loggers)
54 | }
55 |
--------------------------------------------------------------------------------
/utils/collection/range_test.go:
--------------------------------------------------------------------------------
1 | package collection
2 |
3 | import (
4 | "fmt"
5 | "slices"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 |
10 | "github.com/ARM-software/golang-utils/utils/field"
11 | "github.com/ARM-software/golang-utils/utils/reflection"
12 | )
13 |
14 | func TestRange(t *testing.T) {
15 | tests := []struct {
16 | start int
17 | stop int
18 | step *int
19 | expected []int
20 | }{
21 |
22 | {2, 5, nil, []int{2, 3, 4}},
23 | {5, 2, nil, []int{}}, // empty, since stop < start
24 | {2, 10, field.ToOptionalInt(2), []int{2, 4, 6, 8}},
25 | {0, 10, field.ToOptionalInt(3), []int{0, 3, 6, 9}},
26 | {1, 10, field.ToOptionalInt(3), []int{1, 4, 7}},
27 | {10, 2, field.ToOptionalInt(-2), []int{10, 8, 6, 4}},
28 | {5, -1, field.ToOptionalInt(-1), []int{5, 4, 3, 2, 1, 0}},
29 | {0, -5, field.ToOptionalInt(-2), []int{0, -2, -4}},
30 | {0, 5, nil, []int{0, 1, 2, 3, 4}},
31 | {0, 5, field.ToOptionalInt(0), []int{}},
32 | {2, 2, field.ToOptionalInt(1), []int{}},
33 | {2, 2, field.ToOptionalInt(-1), []int{}},
34 | }
35 |
36 | for i := range tests {
37 | test := tests[i]
38 | t.Run(fmt.Sprintf("[%v,%v,%v]", test.start, test.stop, test.step), func(t *testing.T) {
39 | assert.Equal(t, test.expected, Range(test.start, test.stop, test.step))
40 | if reflection.IsEmpty(test.expected) {
41 | assert.Empty(t, slices.Collect(RangeSequence(test.start, test.stop, test.step)))
42 | } else {
43 | assert.Equal(t, test.expected, slices.Collect(RangeSequence(test.start, test.stop, test.step)))
44 | }
45 |
46 | })
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/utils/http/headers/useragent/useragent.go:
--------------------------------------------------------------------------------
1 | package useragent
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/ARM-software/golang-utils/utils/commonerrors"
8 | "github.com/ARM-software/golang-utils/utils/reflection"
9 | )
10 |
11 | // AddValuesToUserAgent extends a user agent string with new elements. See https://en.wikipedia.org/wiki/User-Agent_header#Format_for_human-operated_web_browsers
12 | func AddValuesToUserAgent(userAgent string, elements ...string) (newUserAgent string) {
13 | if len(elements) == 0 {
14 | newUserAgent = userAgent
15 | return
16 | }
17 | newUserAgent = strings.Join(elements, " ")
18 | newUserAgent = strings.TrimSpace(newUserAgent)
19 | if newUserAgent == "" {
20 | newUserAgent = userAgent
21 | return
22 | }
23 | if !reflection.IsEmpty(userAgent) {
24 | newUserAgent = fmt.Sprintf("%v %v", userAgent, newUserAgent)
25 | }
26 | return
27 | }
28 |
29 | // GenerateUserAgentValue generates a user agent value. See https://en.wikipedia.org/wiki/User-Agent_header#Format_for_human-operated_web_browsers
30 | func GenerateUserAgentValue(product string, productVersion string, comment string) (userAgent string, err error) {
31 | if reflection.IsEmpty(product) {
32 | err = commonerrors.UndefinedVariable("product")
33 | return
34 | }
35 | if reflection.IsEmpty(productVersion) {
36 | err = commonerrors.UndefinedVariable("product version")
37 | return
38 | }
39 | userAgent = fmt.Sprintf("%v/%v", product, productVersion)
40 | if !reflection.IsEmpty(comment) {
41 | userAgent = fmt.Sprintf("%v (%v)", userAgent, comment)
42 | }
43 | return
44 | }
45 |
--------------------------------------------------------------------------------
/utils/http/headers/useragent/useragent/useragent.go:
--------------------------------------------------------------------------------
1 | package useragent
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/ARM-software/golang-utils/utils/commonerrors"
8 | "github.com/ARM-software/golang-utils/utils/reflection"
9 | )
10 |
11 | // AddValuesToUserAgent extends a user agent string with new elements. See https://en.wikipedia.org/wiki/User-Agent_header#Format_for_human-operated_web_browsers
12 | func AddValuesToUserAgent(userAgent string, elements ...string) (newUserAgent string) {
13 | if len(elements) == 0 {
14 | newUserAgent = userAgent
15 | return
16 | }
17 | newUserAgent = strings.Join(elements, " ")
18 | newUserAgent = strings.TrimSpace(newUserAgent)
19 | if newUserAgent == "" {
20 | newUserAgent = userAgent
21 | return
22 | }
23 | if !reflection.IsEmpty(userAgent) {
24 | newUserAgent = fmt.Sprintf("%v %v", userAgent, newUserAgent)
25 | }
26 | return
27 | }
28 |
29 | // GenerateUserAgentValue generates a user agent value. See https://en.wikipedia.org/wiki/User-Agent_header#Format_for_human-operated_web_browsers
30 | func GenerateUserAgentValue(product string, productVersion string, comment string) (userAgent string, err error) {
31 | if reflection.IsEmpty(product) {
32 | err = commonerrors.UndefinedVariable("product")
33 | return
34 | }
35 | if reflection.IsEmpty(productVersion) {
36 | err = commonerrors.UndefinedVariable("product version")
37 | return
38 | }
39 | userAgent = fmt.Sprintf("%v/%v", product, productVersion)
40 | if !reflection.IsEmpty(comment) {
41 | userAgent = fmt.Sprintf("%v (%v)", userAgent, comment)
42 | }
43 | return
44 | }
45 |
--------------------------------------------------------------------------------
/utils/parallelisation/mocks/mock_parallelisation.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: io (interfaces: Closer)
3 | //
4 | // Generated by this command:
5 | //
6 | // mockgen -destination=./mocks/mock_parallelisation.go -package=mocks io Closer
7 | //
8 |
9 | // Package mocks is a generated GoMock package.
10 | package mocks
11 |
12 | import (
13 | reflect "reflect"
14 |
15 | gomock "go.uber.org/mock/gomock"
16 | )
17 |
18 | // MockCloser is a mock of Closer interface.
19 | type MockCloser struct {
20 | ctrl *gomock.Controller
21 | recorder *MockCloserMockRecorder
22 | isgomock struct{}
23 | }
24 |
25 | // MockCloserMockRecorder is the mock recorder for MockCloser.
26 | type MockCloserMockRecorder struct {
27 | mock *MockCloser
28 | }
29 |
30 | // NewMockCloser creates a new mock instance.
31 | func NewMockCloser(ctrl *gomock.Controller) *MockCloser {
32 | mock := &MockCloser{ctrl: ctrl}
33 | mock.recorder = &MockCloserMockRecorder{mock}
34 | return mock
35 | }
36 |
37 | // EXPECT returns an object that allows the caller to indicate expected use.
38 | func (m *MockCloser) EXPECT() *MockCloserMockRecorder {
39 | return m.recorder
40 | }
41 |
42 | // Close mocks base method.
43 | func (m *MockCloser) Close() error {
44 | m.ctrl.T.Helper()
45 | ret := m.ctrl.Call(m, "Close")
46 | ret0, _ := ret[0].(error)
47 | return ret0
48 | }
49 |
50 | // Close indicates an expected call of Close.
51 | func (mr *MockCloserMockRecorder) Close() *gomock.Call {
52 | mr.mock.ctrl.T.Helper()
53 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockCloser)(nil).Close))
54 | }
55 |
--------------------------------------------------------------------------------
/utils/resource/resource.go:
--------------------------------------------------------------------------------
1 | package resource
2 |
3 | import (
4 | "io"
5 | "sync"
6 | )
7 |
8 | type closeableResource struct {
9 | io.Closer
10 | mu sync.RWMutex
11 | closeableResource io.Closer
12 | closed bool
13 | description string
14 | }
15 |
16 | func (c *closeableResource) IsClosed() bool {
17 | c.mu.RLock()
18 | defer c.mu.RUnlock()
19 | return c.closed
20 | }
21 |
22 | func (c *closeableResource) Close() error {
23 | c.mu.Lock()
24 | defer c.mu.Unlock()
25 | if c.closeableResource != nil {
26 | err := c.closeableResource.Close()
27 | if err != nil {
28 | return err
29 | }
30 | }
31 | c.closed = true
32 | c.closeableResource = nil
33 | return nil
34 | }
35 |
36 | func (c *closeableResource) String() string {
37 | c.mu.RLock()
38 | defer c.mu.RUnlock()
39 | return c.description
40 | }
41 |
42 | // NewCloseableResource returns a Closeable resource.
43 | func NewCloseableResource(resource io.Closer, description string) ICloseableResource {
44 | return &closeableResource{
45 | closeableResource: resource,
46 | closed: false,
47 | description: description,
48 | }
49 | }
50 |
51 | type closeableNilResource struct {
52 | }
53 |
54 | func (c *closeableNilResource) Close() error {
55 | return nil
56 | }
57 |
58 | func (c *closeableNilResource) String() string {
59 | return "non closeable resource"
60 | }
61 |
62 | func (c *closeableNilResource) IsClosed() bool {
63 | return false
64 | }
65 |
66 | // NewNonCloseableResource returns a resource which cannot be closed.
67 | func NewNonCloseableResource() ICloseableResource {
68 | return &closeableNilResource{}
69 | }
70 |
--------------------------------------------------------------------------------
/utils/http/pooled_client.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package http
6 |
7 | import (
8 | "net/http"
9 |
10 | "github.com/hashicorp/go-cleanhttp"
11 | )
12 |
13 | // PooledClient is an HTTP client similar to
14 | // http.Client, but with a shared Transport and different configuration values.
15 | // It is based on https://github.com/hashicorp/go-cleanhttp which ensures the client configuration is only set for the current use case and not the whole project (i.e. no global variable)
16 | type PooledClient struct {
17 | GenericClient
18 | }
19 |
20 | // NewDefaultPooledClient returns a new HTTP client with similar default values to
21 | // http.Client, but with a shared Transport.
22 | func NewDefaultPooledClient() IClient {
23 | return NewPooledClient(DefaultHTTPClientConfiguration())
24 | }
25 |
26 | // NewFastPooledClient returns a new HTTP client with similar default values to
27 | // fast http client https://github.com/valyala/fasthttp.
28 | func NewFastPooledClient() IClient {
29 | return NewPooledClient(FastHTTPClientConfiguration())
30 | }
31 |
32 | // NewPooledClient returns a new HTTP client using the configuration passed as argument.
33 | // Do not use this function for
34 | // transient clients as it can leak file descriptors over time. Only use this
35 | // for clients that will be re-used for the same host(s).
36 | func NewPooledClient(cfg *HTTPClientConfiguration) IClient {
37 | transport := cleanhttp.DefaultPooledTransport()
38 | setTransportConfiguration(cfg, transport)
39 | return NewGenericClient(&http.Client{
40 | Transport: transport,
41 | })
42 | }
43 |
--------------------------------------------------------------------------------
/utils/proc/ps_posix.go:
--------------------------------------------------------------------------------
1 | //go:build !windows
2 |
3 | /*
4 | * Copyright (C) 2020-2024 Arm Limited or its affiliates and Contributors. All rights reserved.
5 | * SPDX-License-Identifier: Apache-2.0
6 | */
7 |
8 | package proc
9 |
10 | import (
11 | "context"
12 | "syscall"
13 |
14 | "github.com/ARM-software/golang-utils/utils/commonerrors"
15 | "github.com/ARM-software/golang-utils/utils/parallelisation"
16 | )
17 |
18 | func getpgid(pid int) (gpid int, err error) {
19 | gpid, err = syscall.Getpgid(pid)
20 | if err != nil {
21 | err = commonerrors.WrapErrorf(commonerrors.ErrUnexpected, err, "could not get pgid of '%v'", pid)
22 | return
23 | }
24 |
25 | return
26 | }
27 |
28 | func killGroup(ctx context.Context, pid int32) (err error) {
29 | err = parallelisation.DetermineContextError(ctx)
30 | if err != nil {
31 | return
32 | }
33 | // see https://varunksaini.com/posts/kiling-processes-in-go/
34 | pgid, err := getpgid(int(pid))
35 | if err != nil {
36 | return
37 | }
38 | // kill a whole process group by sending a signal to -xyz where xyz is the pgid
39 | // http://unix.stackexchange.com/questions/14815/process-descendants
40 | if pgid != int(pid) {
41 | err = commonerrors.Newf(commonerrors.ErrUnexpected, "process #%v is not group leader", pid)
42 | return
43 | }
44 | err = ConvertProcessError(syscall.Kill(-pgid, syscall.SIGKILL))
45 | return
46 | }
47 |
48 | func getGroupProcesses(ctx context.Context, pid int) (pids []int, err error) {
49 | pgid, err := getpgid(pid)
50 | if err != nil {
51 | err = commonerrors.WrapErrorf(commonerrors.ErrUnexpected, err, "could not get group PID for '%v'", pid)
52 | return
53 | }
54 | pids = append(pids, pgid)
55 | return
56 | }
57 |
--------------------------------------------------------------------------------
/utils/logs/logrus_logger.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | // Package logs defines loggers for use in projects.
7 | package logs
8 |
9 | import (
10 | "github.com/rifflock/lfshook"
11 | "github.com/sirupsen/logrus"
12 |
13 | "github.com/ARM-software/golang-utils/utils/commonerrors"
14 | "github.com/ARM-software/golang-utils/utils/logs/logrimp"
15 | "github.com/ARM-software/golang-utils/utils/reflection"
16 | )
17 |
18 | // NewLogrusLogger returns a logger which uses logrus logger (https://github.com/Sirupsen/logrus)
19 | func NewLogrusLogger(logrusL *logrus.Logger, loggerSource string) (loggers Loggers, err error) {
20 | if logrusL == nil {
21 | err = commonerrors.ErrNoLogger
22 | return
23 | }
24 | return NewLogrLogger(logrimp.NewLogrusLogger(logrusL), loggerSource)
25 | }
26 |
27 | // NewLogrusLoggerWithFileHook returns a logger which uses a logrus logger (https://github.com/Sirupsen/logrus) and writes the logs to `logFilePath`
28 | func NewLogrusLoggerWithFileHook(logrusL *logrus.Logger, loggerSource string, logFilePath string) (loggers Loggers, err error) {
29 | if logrusL == nil {
30 | err = commonerrors.ErrNoLogger
31 | return
32 | }
33 | if reflection.IsEmpty(logFilePath) {
34 | err = commonerrors.New(commonerrors.ErrInvalidDestination, "missing file destination")
35 | return
36 | }
37 | pathMap := lfshook.PathMap{
38 | logrus.InfoLevel: logFilePath,
39 | logrus.ErrorLevel: logFilePath,
40 | }
41 | logrusL.Hooks.Add(lfshook.NewHook(
42 | pathMap,
43 | &logrus.JSONFormatter{},
44 | ))
45 | return NewLogrusLogger(logrusL, loggerSource)
46 | }
47 |
--------------------------------------------------------------------------------
/utils/sharedcache/interface.go:
--------------------------------------------------------------------------------
1 | package sharedcache
2 |
3 | import (
4 | "context"
5 | "time"
6 | )
7 |
8 | // Mocks are generated using `go generate ./...`
9 | // Add interfaces to the following command for a mock to be generated
10 | //go:generate go tool mockgen -destination=../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/$GOPACKAGE ISharedCacheRepository
11 |
12 | // ISharedCacheRepository defines a cache stored on a remote location and shared by separate processes.
13 | type ISharedCacheRepository interface {
14 | // GenerateKey generates a unique key based on key elements `elems`.
15 | GenerateKey(elems ...string) string
16 | // Fetch downloads and installs files from the cache[`key`] to `dest`.
17 | Fetch(ctx context.Context, key, dest string) error
18 | // Store uploads files from `src` to cache[`key`].
19 | Store(ctx context.Context, key, src string) error
20 | // CleanEntry cleans up cache[`key`]. The key is still present in the cache.
21 | CleanEntry(ctx context.Context, key string) error
22 | // RemoveEntry removes cache[`key`] entry. The key is then no longer present in the cache.
23 | RemoveEntry(ctx context.Context, key string) error
24 | // GetEntryAge returns the age of the cache[`key`] entry
25 | GetEntryAge(ctx context.Context, key string) (age time.Duration, err error)
26 | // SetEntryAge sets the age of the cache[`key`] entry. Mostly for testing.
27 | SetEntryAge(ctx context.Context, key string, age time.Duration) error
28 | // GetEntries returns all cache entries.
29 | GetEntries(ctx context.Context) (entries []string, err error)
30 | // EntriesCount returns cache entries count
31 | EntriesCount(ctx context.Context) (int64, error)
32 | }
33 |
--------------------------------------------------------------------------------
/utils/cache/filecache/entry.go:
--------------------------------------------------------------------------------
1 | package filecache
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/ARM-software/golang-utils/utils/filesystem"
8 | )
9 |
10 | type CacheEntry struct {
11 | cachePath string
12 | cacheFs filesystem.FS
13 | ttl time.Duration
14 | expiration time.Time
15 | }
16 |
17 | func (e *CacheEntry) Copy(ctx context.Context, destFs filesystem.FS, destPath string) error {
18 | // Copying to a temp location first, then move to the original location.
19 | // This prevents the cache to present a transient file/dir that is still in the copying process.
20 | tmpDir, err := destFs.TempDirInTempDir("filecache-tmp")
21 | if err != nil {
22 | return err
23 | }
24 |
25 | tmpPath := filesystem.FilePathJoin(destFs, tmpDir, filesystem.FilePathBase(e.cacheFs, e.cachePath))
26 | if err := filesystem.CopyBetweenFS(ctx, e.cacheFs, e.cachePath, destFs, tmpPath); err != nil {
27 | return err
28 | }
29 |
30 | err = destFs.Move(tmpPath, destPath)
31 | if err != nil {
32 | return err
33 | }
34 |
35 | return nil
36 | }
37 |
38 | func (e *CacheEntry) Delete(ctx context.Context) error {
39 | if err := e.cacheFs.RemoveWithPrivileges(ctx, e.cachePath); err != nil {
40 | return err
41 | }
42 |
43 | return nil
44 | }
45 |
46 | func (e *CacheEntry) IsExpired() bool {
47 | return time.Now().After(e.expiration)
48 | }
49 |
50 | func (e *CacheEntry) ExtendLifetime() {
51 | e.expiration = time.Now().Add(e.ttl)
52 | }
53 |
54 | func NewCacheEntry(cacheFilesystem filesystem.FS, path string, ttl time.Duration) ICacheEntry {
55 | return &CacheEntry{
56 | cachePath: path,
57 | cacheFs: cacheFilesystem,
58 | ttl: ttl,
59 | expiration: time.Now().Add(ttl),
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/utils/logs/hclog_logger.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | // Package logs defines loggers for use in projects.
7 | package logs
8 |
9 | import (
10 | "github.com/hashicorp/go-hclog"
11 |
12 | "github.com/ARM-software/golang-utils/utils/commonerrors"
13 | "github.com/ARM-software/golang-utils/utils/logs/logrimp"
14 | )
15 |
16 | // NewHclogLogger returns a logger which uses hclog logger (https://github.com/hashicorp/go-hclog)
17 | func NewHclogLogger(hclogL hclog.Logger, loggerSource string) (loggers Loggers, err error) {
18 | if hclogL == nil {
19 | err = commonerrors.ErrNoLogger
20 | return
21 | }
22 | return NewLogrLogger(logrimp.NewHclogLogger(hclogL), loggerSource)
23 | }
24 |
25 | // NewHclogWrapper returns an hclog logger from a Loggers logger
26 | func NewHclogWrapper(loggers Loggers) (hclogL hclog.Logger, err error) {
27 | if loggers == nil {
28 | err = commonerrors.ErrNoLogger
29 | return
30 | }
31 | intercept := hclog.NewInterceptLogger(nil)
32 |
33 | info, err := NewInfoWriterFromLoggers(loggers)
34 | if err != nil {
35 | return
36 | }
37 | errL, err := NewErrorWriterFromLoggers(loggers)
38 | if err != nil {
39 | return
40 | }
41 |
42 | sinkErr := hclog.NewSinkAdapter(&hclog.LoggerOptions{
43 | Level: hclog.Warn,
44 | Output: errL,
45 | DisableTime: true,
46 | })
47 | sinkInfo := hclog.NewSinkAdapter(&hclog.LoggerOptions{
48 | Level: hclog.Info,
49 | Output: info,
50 | DisableTime: true,
51 | })
52 |
53 | intercept.RegisterSink(sinkErr)
54 | intercept.RegisterSink(sinkInfo)
55 |
56 | hclogL = intercept
57 | return
58 | }
59 |
--------------------------------------------------------------------------------
/utils/filesystem/filetimes.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package filesystem
6 |
7 | import (
8 | "os"
9 | "time"
10 |
11 | fileTimes "github.com/djherbis/times"
12 |
13 | "github.com/ARM-software/golang-utils/utils/commonerrors"
14 | )
15 |
16 | func DetermineFileTimes(info os.FileInfo) (times FileTimeInfo, err error) {
17 | if info == nil {
18 | err = commonerrors.New(commonerrors.ErrUndefined, "no file information defined")
19 | return
20 | }
21 | if info.Sys() == nil {
22 | times = newDefaultTimeInfo(info)
23 | } else {
24 | times = &genericTimeInfo{fileTimes.Get(info)}
25 | }
26 | return
27 | }
28 |
29 | type defaultTimeInfo struct {
30 | modTime time.Time
31 | }
32 |
33 | func (i *defaultTimeInfo) ModTime() time.Time {
34 | return i.modTime
35 | }
36 | func (i *defaultTimeInfo) AccessTime() time.Time {
37 | return time.Now()
38 | }
39 | func (i *defaultTimeInfo) ChangeTime() time.Time {
40 | return time.Now()
41 | }
42 | func (i *defaultTimeInfo) BirthTime() time.Time {
43 | return time.Now()
44 | }
45 | func (i *defaultTimeInfo) HasChangeTime() bool {
46 | return false
47 | }
48 | func (i *defaultTimeInfo) HasBirthTime() bool {
49 | return false
50 | }
51 |
52 | func (i *defaultTimeInfo) HasAccessTime() bool {
53 | return false
54 | }
55 |
56 | func newDefaultTimeInfo(f os.FileInfo) (info *defaultTimeInfo) {
57 | info = &defaultTimeInfo{}
58 | if f != nil {
59 | info.modTime = f.ModTime()
60 | }
61 | return
62 | }
63 |
64 | type genericTimeInfo struct {
65 | fileTimes.Timespec
66 | }
67 |
68 | func (i *genericTimeInfo) HasAccessTime() bool {
69 | return false
70 | }
71 |
--------------------------------------------------------------------------------
/.github/workflows/dependabot.yml:
--------------------------------------------------------------------------------
1 | name: Dependabot
2 | on: pull_request_target
3 | permissions:
4 | pull-requests: write
5 | issues: write
6 | repository-projects: write
7 | contents: write
8 | jobs:
9 | dependabot:
10 | runs-on: ubuntu-latest
11 | if: ${{ github.actor == 'dependabot[bot]' }}
12 | steps:
13 | # Checkout with full history for to allow compare with base branch
14 | - uses: actions/checkout@v4
15 | with:
16 | fetch-depth: 0
17 | - uses: actions/setup-python@v5
18 | - uses: FranzDiebold/github-env-vars-action@v2
19 | - name: Install CI/CD tools
20 | run: pip install continuous-delivery-scripts && pip list
21 | - name: Dependabot metadata
22 | id: dependabot-metadata
23 | uses: dependabot/fetch-metadata@v1.6.0
24 | with:
25 | github-token: "${{ secrets.GITHUB_TOKEN }}"
26 | - name: Add a label
27 | run: gh pr edit "$PR_URL" --add-label "bot"
28 | env:
29 | PR_URL: ${{github.event.pull_request.html_url}}
30 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
31 | - name: Approve the PR
32 | run: gh pr review --approve "$PR_URL"
33 | env:
34 | PR_URL: ${{github.event.pull_request.html_url}}
35 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
36 | - name: Assert news
37 | run: cd-assert-news -b ${CI_ACTION_REF_NAME}
38 | env:
39 | GIT_TOKEN: ${{ secrets.GIT_SECRET }}
40 | - name: Auto-merge the PR
41 | run: gh pr merge --auto --squash --body "Dependency upgrade $PR_URL" --merge "$PR_URL"
42 | env:
43 | PR_URL: ${{github.event.pull_request.html_url}}
44 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
45 |
--------------------------------------------------------------------------------
/utils/filesystem/extendedosfs.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | package filesystem
7 |
8 | import (
9 | "context"
10 | "os"
11 | "syscall"
12 |
13 | "github.com/spf13/afero"
14 |
15 | "github.com/ARM-software/golang-utils/utils/commonerrors"
16 | "github.com/ARM-software/golang-utils/utils/platform"
17 | )
18 |
19 | // ExtendedOsFs extends afero.OsFs and is a Fs implementation that uses functions provided by the os package.
20 | type ExtendedOsFs struct {
21 | afero.OsFs
22 | }
23 |
24 | func (c *ExtendedOsFs) Remove(name string) (err error) {
25 | // The following is to ensure sockets are correctly removed
26 | // https://stackoverflow.com/questions/16681944/how-to-reliably-unlink-a-unix-domain-socket-in-go-programming-language
27 | err = commonerrors.Ignore(ConvertFileSystemError(syscall.Unlink(name)), commonerrors.ErrNotFound)
28 | err = commonerrors.IgnoreCorrespondTo(err, "is a directory")
29 | if err != nil {
30 | return
31 | }
32 |
33 | err = commonerrors.Ignore(ConvertFileSystemError(c.OsFs.Remove(name)), commonerrors.ErrNotFound)
34 | return
35 | }
36 |
37 | func (c *ExtendedOsFs) ChownIfPossible(name string, uid int, gid int) error {
38 | return ConvertFileSystemError(c.Chown(name, uid, gid))
39 | }
40 |
41 | func (c *ExtendedOsFs) LinkIfPossible(oldname, newname string) (err error) {
42 | return ConvertFileSystemError(os.Link(oldname, newname))
43 | }
44 |
45 | func (c *ExtendedOsFs) ForceRemoveIfPossible(path string) error {
46 | return ConvertFileSystemError(platform.RemoveWithPrivileges(context.Background(), path))
47 | }
48 |
49 | func NewExtendedOsFs() afero.Fs {
50 | return &ExtendedOsFs{}
51 | }
52 |
--------------------------------------------------------------------------------
/utils/logs/json_logger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "testing"
9 | "time"
10 |
11 | "github.com/stretchr/testify/require"
12 | "go.uber.org/goleak"
13 | )
14 |
15 | func TestLogMessage(t *testing.T) {
16 | defer goleak.VerifyNone(t)
17 | t.Run("with writer closing", func(t *testing.T) {
18 | loggers, err := NewJSONLogger(NewStdWriterWithSource(), "Test", "TestLogMessage")
19 | require.NoError(t, err)
20 | testLog(t, loggers)
21 | })
22 | t.Run("without writer closing", func(t *testing.T) {
23 | writer := NewStdWriterWithSource()
24 | defer func() { require.NoError(t, writer.Close()) }()
25 | loggers, err := NewJSONLoggerWithWriter(writer, "Test", "TestLogMessage")
26 | require.NoError(t, err)
27 | testLog(t, loggers)
28 | })
29 | }
30 |
31 | func TestLogMessageToSlowLogger(t *testing.T) {
32 | defer goleak.VerifyNone(t)
33 | stdloggers, err := NewStdLogger("ERR:")
34 | require.NoError(t, err)
35 |
36 | t.Run("with writer closing", func(t *testing.T) {
37 | loggers, err := NewJSONLoggerForSlowWriter(NewTestSlowWriter(t), 1024, 2*time.Millisecond, "Test", "TestLogMessageToSlowLogger", stdloggers)
38 | require.NoError(t, err)
39 | testLog(t, loggers)
40 | time.Sleep(100 * time.Millisecond)
41 | })
42 | t.Run("without writer closing", func(t *testing.T) {
43 | writer := NewTestSlowWriter(t)
44 | defer func() { require.NoError(t, writer.Close()) }()
45 | loggers, err := NewJSONLoggerForSlowWriterWithoutClosingWriter(writer, 1024, 2*time.Millisecond, "Test", "TestLogMessageToSlowLogger", stdloggers)
46 | require.NoError(t, err)
47 | testLog(t, loggers)
48 | time.Sleep(100 * time.Millisecond)
49 | })
50 | }
51 |
--------------------------------------------------------------------------------
/utils/collection/modify_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package collection
6 |
7 | import (
8 | "fmt"
9 | "testing"
10 |
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func TestRemove(t *testing.T) {
15 | // Given a list of strings and a string
16 | // Returns the list without the string
17 | tests := []struct {
18 | SrcList []string
19 | ToDelete []string
20 | ExpectedList []string
21 | }{
22 | {
23 | SrcList: []string{"a", "b", "c", "d"},
24 | ToDelete: []string{"c"},
25 | ExpectedList: []string{"a", "b", "d"},
26 | },
27 | {
28 | SrcList: []string{"a", "b", "c", "d"},
29 | ToDelete: []string{"h"},
30 | ExpectedList: []string{"a", "b", "c", "d"},
31 | },
32 | {
33 | SrcList: []string{"a", "b", "c", "d"},
34 | ToDelete: []string{"d"},
35 | ExpectedList: []string{"a", "b", "c"},
36 | },
37 | {
38 | SrcList: []string{"d", "d", "d", "d"},
39 | ToDelete: []string{"d"},
40 | ExpectedList: []string{},
41 | },
42 | {
43 | SrcList: []string{},
44 | ToDelete: []string{"d"},
45 | ExpectedList: []string{},
46 | },
47 | {
48 | SrcList: []string{},
49 | ToDelete: []string{"d", "e"},
50 | ExpectedList: []string{},
51 | },
52 | {
53 | SrcList: []string{"a", "b", "c", "d"},
54 | ToDelete: []string{"a", "b", "d"},
55 | ExpectedList: []string{"c"},
56 | },
57 | }
58 | for i := range tests {
59 | test := tests[i]
60 | t.Run(fmt.Sprintf("test_%v", i), func(t *testing.T) {
61 | t.Parallel()
62 | newList := Remove(test.SrcList, test.ToDelete...)
63 | assert.Equal(t, test.ExpectedList, newList)
64 | })
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/utils/proc/errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2024 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package proc
6 |
7 | import (
8 | "os"
9 | "os/exec"
10 | "syscall"
11 |
12 | "github.com/shirou/gopsutil/v4/process"
13 |
14 | "github.com/ARM-software/golang-utils/utils/commonerrors"
15 | )
16 |
17 | const (
18 | errKilledProcess = "signal: killed"
19 | errTerminatedProcess = "signal: terminated"
20 | errAccessDenied = "Access is denied"
21 | errNotImplemented = "not implemented"
22 | )
23 |
24 | func ConvertProcessError(err error) error {
25 | err = commonerrors.ConvertContextError(err)
26 | switch {
27 | case err == nil:
28 | return err
29 | case commonerrors.CorrespondTo(err, errKilledProcess, errTerminatedProcess):
30 | return os.ErrProcessDone
31 | case commonerrors.Any(err, syscall.ESRCH):
32 | // ESRCH is "no such process", meaning the process has already exited.
33 | return nil
34 | case commonerrors.Any(err, exec.ErrWaitDelay):
35 | return commonerrors.WrapError(commonerrors.ErrTimeout, err, "")
36 | case commonerrors.Any(err, exec.ErrDot, exec.ErrNotFound):
37 | return commonerrors.WrapError(commonerrors.ErrNotFound, err, "")
38 | case commonerrors.Any(process.ErrorNotPermitted):
39 | return commonerrors.WrapError(commonerrors.ErrForbidden, err, "")
40 | case commonerrors.Any(process.ErrorProcessNotRunning):
41 | return commonerrors.WrapError(commonerrors.ErrNotFound, err, "")
42 | case commonerrors.CorrespondTo(err, errAccessDenied):
43 | return commonerrors.WrapError(commonerrors.ErrNotFound, err, "")
44 | case commonerrors.CorrespondTo(err, errNotImplemented):
45 | return commonerrors.WrapError(commonerrors.ErrNotImplemented, err, "")
46 | default:
47 | return err
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/utils/semver/semver.go:
--------------------------------------------------------------------------------
1 | package semver
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "golang.org/x/mod/semver"
8 |
9 | "github.com/ARM-software/golang-utils/utils/commonerrors"
10 | )
11 |
12 | // CanonicalWithGoPrefix will return the canonical form of the version with the .MAJOR .MINOR and .PATCH whilst discarding build information
13 | // It will return an error if the version is not valid semver (unlike Canonical() in golang.org/x/mod/semver)
14 | // It will prepend 'v' if necessary for compatibility with 'golang.org/x/mod/semver'. Use Canonical() if you don't want the 'v' prefix
15 | func CanonicalWithGoPrefix(v string) (canonical string, err error) {
16 | v = strings.TrimSpace(v)
17 | if v == "" {
18 | err = commonerrors.UndefinedParameter("no version was supplied")
19 | return
20 | }
21 |
22 | if v[0] != 'v' {
23 | v = fmt.Sprint("v", v)
24 | }
25 |
26 | canonical = semver.Canonical(v)
27 | if canonical == "" {
28 | err = commonerrors.Newf(commonerrors.ErrInvalid, "could not parse '%v' as a semantic version", v)
29 | }
30 |
31 | return
32 | }
33 |
34 | // Canonical will return the canonical form of the version with the .MAJOR .MINOR and .PATCH whilst discarding build information
35 | // It will return an error if the version is not valid semver (unlike Canonical() in golang.org/x/mod/semver)
36 | // Use CanonicalWithGoPrefix() if you want the 'v' prefix for compatibility with golang.org/x/mod/semver
37 | func Canonical(v string) (canonical string, err error) {
38 | canonical, err = CanonicalWithGoPrefix(v)
39 | if err != nil {
40 | return
41 | }
42 |
43 | canonical = TrimGoPrefix(canonical)
44 | return
45 | }
46 |
47 | // TrimGoPrefix will trim the 'v' prefix from a string
48 | func TrimGoPrefix(v string) string {
49 | if v == "" || v[0] != 'v' {
50 | return v
51 | }
52 | return v[1:]
53 | }
54 |
--------------------------------------------------------------------------------
/utils/http/retry_configuration.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package http
6 |
7 | import (
8 | "github.com/ARM-software/golang-utils/utils/retry"
9 | )
10 |
11 | // RetryPolicyConfiguration was moved to the `retry` module. Nonetheless, it was redefined here to avoid breaking changes
12 | type RetryPolicyConfiguration = retry.RetryPolicyConfiguration
13 |
14 | // DefaultNoRetryPolicyConfiguration defines a configuration for no retry being performed.
15 | func DefaultNoRetryPolicyConfiguration() *RetryPolicyConfiguration {
16 | return retry.DefaultNoRetryPolicyConfiguration()
17 | }
18 |
19 | // DefaultBasicRetryPolicyConfiguration defines a configuration for basic retries i.e. retrying straight after a failure for maximum 4 attempts.
20 | func DefaultBasicRetryPolicyConfiguration() *RetryPolicyConfiguration {
21 | return retry.DefaultBasicRetryPolicyConfiguration()
22 | }
23 |
24 | // DefaultRobustRetryPolicyConfiguration defines a configuration for basic retries but considering any `Retry-After` being returned by server.
25 | func DefaultRobustRetryPolicyConfiguration() *RetryPolicyConfiguration {
26 | return retry.DefaultRobustRetryPolicyConfiguration()
27 | }
28 |
29 | // DefaultExponentialBackoffRetryPolicyConfiguration defines a configuration for retries with exponential backoff.
30 | func DefaultExponentialBackoffRetryPolicyConfiguration() *RetryPolicyConfiguration {
31 | return retry.DefaultExponentialBackoffRetryPolicyConfiguration()
32 | }
33 |
34 | // DefaultLinearBackoffRetryPolicyConfiguration defines a configuration for retries with linear backoff.
35 | func DefaultLinearBackoffRetryPolicyConfiguration() *RetryPolicyConfiguration {
36 | return retry.DefaultLinearBackoffRetryPolicyConfiguration()
37 | }
38 |
--------------------------------------------------------------------------------
/utils/semver/sanitisation_test.go:
--------------------------------------------------------------------------------
1 | package semver
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | "github.com/ARM-software/golang-utils/utils/commonerrors"
9 | "github.com/ARM-software/golang-utils/utils/commonerrors/errortest"
10 | )
11 |
12 | func TestSanitiseMajor(t *testing.T) {
13 | t.Run("No Version", func(t *testing.T) {
14 | v, err := SanitiseVersionMajor("")
15 | errortest.AssertError(t, err, commonerrors.ErrUndefined)
16 | assert.Empty(t, v)
17 | })
18 |
19 | t.Run("Valid Version", func(t *testing.T) {
20 | v, err := SanitiseVersionMajor("1.2.3")
21 | assert.NoError(t, err)
22 | assert.Equal(t, "1", v)
23 | })
24 |
25 | t.Run("Valid Version 2", func(t *testing.T) {
26 | v, err := SanitiseVersionMajor("v1.2.3")
27 | assert.NoError(t, err)
28 | assert.Equal(t, "1", v)
29 | })
30 |
31 | t.Run("Invalid Version", func(t *testing.T) {
32 | v, err := SanitiseVersionMajor("aaaaaa")
33 | errortest.AssertError(t, err, commonerrors.ErrInvalid)
34 | assert.Empty(t, v)
35 | })
36 | }
37 |
38 | func TestSanitiseMajorMinor(t *testing.T) {
39 | t.Run("No Version", func(t *testing.T) {
40 | v, err := SanitiseVersionMajorMinor("")
41 | errortest.AssertError(t, err, commonerrors.ErrUndefined)
42 | assert.Empty(t, v)
43 | })
44 |
45 | t.Run("Valid Version", func(t *testing.T) {
46 | v, err := SanitiseVersionMajorMinor("1.2.3")
47 | assert.NoError(t, err)
48 | assert.Equal(t, "1.2", v)
49 | })
50 |
51 | t.Run("Valid Version 2", func(t *testing.T) {
52 | v, err := SanitiseVersionMajorMinor("v1.2.3")
53 | assert.NoError(t, err)
54 | assert.Equal(t, "1.2", v)
55 | })
56 |
57 | t.Run("Invalid Version", func(t *testing.T) {
58 | v, err := SanitiseVersionMajorMinor("aaaaaa")
59 | errortest.AssertError(t, err, commonerrors.ErrInvalid)
60 | assert.Empty(t, v)
61 | })
62 | }
63 |
--------------------------------------------------------------------------------
/utils/mocks/mock_supervisor.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: github.com/ARM-software/golang-utils/utils/subprocess/supervisor (interfaces: ISupervisor)
3 | //
4 | // Generated by this command:
5 | //
6 | // mockgen -destination=../../mocks/mock_supervisor.go -package=mocks github.com/ARM-software/golang-utils/utils/subprocess/supervisor ISupervisor
7 | //
8 |
9 | // Package mocks is a generated GoMock package.
10 | package mocks
11 |
12 | import (
13 | context "context"
14 | reflect "reflect"
15 |
16 | gomock "go.uber.org/mock/gomock"
17 | )
18 |
19 | // MockISupervisor is a mock of ISupervisor interface.
20 | type MockISupervisor struct {
21 | ctrl *gomock.Controller
22 | recorder *MockISupervisorMockRecorder
23 | isgomock struct{}
24 | }
25 |
26 | // MockISupervisorMockRecorder is the mock recorder for MockISupervisor.
27 | type MockISupervisorMockRecorder struct {
28 | mock *MockISupervisor
29 | }
30 |
31 | // NewMockISupervisor creates a new mock instance.
32 | func NewMockISupervisor(ctrl *gomock.Controller) *MockISupervisor {
33 | mock := &MockISupervisor{ctrl: ctrl}
34 | mock.recorder = &MockISupervisorMockRecorder{mock}
35 | return mock
36 | }
37 |
38 | // EXPECT returns an object that allows the caller to indicate expected use.
39 | func (m *MockISupervisor) EXPECT() *MockISupervisorMockRecorder {
40 | return m.recorder
41 | }
42 |
43 | // Run mocks base method.
44 | func (m *MockISupervisor) Run(ctx context.Context) error {
45 | m.ctrl.T.Helper()
46 | ret := m.ctrl.Call(m, "Run", ctx)
47 | ret0, _ := ret[0].(error)
48 | return ret0
49 | }
50 |
51 | // Run indicates an expected call of Run.
52 | func (mr *MockISupervisorMockRecorder) Run(ctx any) *gomock.Call {
53 | mr.mock.ctrl.T.Helper()
54 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockISupervisor)(nil).Run), ctx)
55 | }
56 |
--------------------------------------------------------------------------------
/utils/parallelisation/cancel_functions.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | package parallelisation
7 |
8 | import "context"
9 |
10 | type CancelFunctionStore struct {
11 | ExecutionGroup[context.CancelFunc]
12 | }
13 |
14 | func (s *CancelFunctionStore) RegisterCancelFunction(cancel ...context.CancelFunc) {
15 | s.RegisterFunction(cancel...)
16 | }
17 |
18 | func (s *CancelFunctionStore) RegisterCancelStore(store *CancelFunctionStore) {
19 | if store == nil {
20 | return
21 | }
22 | s.RegisterCancelFunction(func() {
23 | store.Cancel()
24 | })
25 | }
26 |
27 | func (s *CancelFunctionStore) Clone() IExecutionGroup[context.CancelFunc] {
28 | g := NewCancelFunctionsStore(s.options.Options()...)
29 | s.CopyFunctions(g)
30 | return g
31 | }
32 |
33 | // Cancel will execute the cancel functions in the store. Any errors will be ignored and Execute() is recommended if you need to know if a cancellation failed
34 | func (s *CancelFunctionStore) Cancel() {
35 | _ = s.Execute(context.Background())
36 | }
37 |
38 | func (s *CancelFunctionStore) Len() int {
39 | return s.ExecutionGroup.Len()
40 | }
41 |
42 | // NewCancelFunctionsStore creates a store for cancel functions. Whatever the options passed, all cancel functions will be executed and cleared. In other words, options `RetainAfterExecution` and `StopOnFirstError` would be discarded if selected to create the Cancel store
43 | func NewCancelFunctionsStore(options ...StoreOption) *CancelFunctionStore {
44 | return &CancelFunctionStore{
45 | ExecutionGroup: *NewExecutionGroup[context.CancelFunc](func(ctx context.Context, cancelFunc context.CancelFunc) error {
46 | return WrapCancelToContextualFunc(cancelFunc)(ctx)
47 | }, append(options, ClearAfterExecution, ExecuteAll)...),
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/utils/commonerrors/errortest/testing.go:
--------------------------------------------------------------------------------
1 | package errortest
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 |
9 | "github.com/ARM-software/golang-utils/utils/commonerrors"
10 | )
11 |
12 | // AssertError asserts that the error is matching one of the `expectedErrors`
13 | // This is a wrapper for commonerrors.Any.
14 | func AssertError(t *testing.T, err error, expectedErrors ...error) bool {
15 | if commonerrors.Any(err, expectedErrors...) {
16 | return true
17 | }
18 | return assert.Fail(t, fmt.Sprintf("Failed error assertion:\n actual: %v\n expected: %+v", err, expectedErrors))
19 | }
20 |
21 | // AssertErrorDescription asserts that the error description corresponds to one of the `expectedErrorDescriptions`
22 | // This is a wrapper for commonerrors.CorrespondTo.
23 | func AssertErrorDescription(t *testing.T, err error, expectedErrorDescriptions ...string) bool {
24 | if commonerrors.CorrespondTo(err, expectedErrorDescriptions...) {
25 | return true
26 | }
27 | return assert.Fail(t, fmt.Sprintf("Failed error description assertion:\n actual: %v\n expected: %+v", err, expectedErrorDescriptions))
28 | }
29 |
30 | // RequireError requires that the error is matching one of the `expectedErrors`
31 | // This is a wrapper for commonerrors.Any.
32 | func RequireError(t *testing.T, err error, expectedErrors ...error) {
33 | t.Helper()
34 | if commonerrors.Any(err, expectedErrors...) {
35 | return
36 | }
37 | t.FailNow()
38 | }
39 |
40 | // RequireErrorDescription requires that the error description corresponds to one of the `expectedErrorDescriptions`
41 | // This is a wrapper for commonerrors.CorrespondTo.
42 | func RequireErrorDescription(t *testing.T, err error, expectedErrorDescriptions ...string) {
43 | t.Helper()
44 | if commonerrors.CorrespondTo(err, expectedErrorDescriptions...) {
45 | return
46 | }
47 | t.FailNow()
48 | }
49 |
--------------------------------------------------------------------------------
/utils/subprocess/command/cmd_test.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/go-faker/faker/v4"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestCommandAsDifferentUser_Redefine(t *testing.T) {
12 | assert.Equal(t, "sudo test 1 2 3", Sudo().RedefineInShellForm("test", "1", "2", "3"))
13 | name := faker.Username()
14 | assert.Equal(t, fmt.Sprintf("su %v test 1 2 3", name), Su(name).RedefineInShellForm("test", "1", "2", "3"))
15 | name = faker.Username()
16 | assert.Equal(t, fmt.Sprintf("gosu %v test 1 2 3", name), Gosu(name).RedefineInShellForm("test", "1", "2", "3"))
17 | assert.Equal(t, fmt.Sprintf("runas /user:%v test 1 2 3", name), RunAs(name).RedefineInShellForm("test", "1", "2", "3"))
18 | assert.Equal(t, "elevate test", Elevate().RedefineInShellForm("test"))
19 | assert.Equal(t, "shellrunas /quiet test", ShellRunAs().RedefineInShellForm("test"))
20 | assert.Equal(t, "test 1 2 3", NewCommandAsDifferentUser().RedefineInShellForm("test", "1", "2", "3"))
21 | assert.Equal(t, "test", Me().RedefineInShellForm("test"))
22 | assert.Empty(t, Me().RedefineInShellForm(""))
23 | cmd, args := Me().Redefine("test")
24 | assert.Equal(t, "test", cmd)
25 | assert.Empty(t, args)
26 | }
27 |
28 | func TestCommandAsDifferentUser_Prepend(t *testing.T) {
29 | name := faker.Username()
30 | assert.Equal(t, fmt.Sprintf("sudo gosu %v test 1 2 3", name), Gosu(name).Prepend(Sudo()).RedefineInShellForm("test", "1", "2", "3"))
31 | name = faker.Username()
32 | assert.Equal(t, fmt.Sprintf("sudo gosu %v test", name), Gosu(name).Prepend(Sudo()).RedefineInShellForm("test"))
33 | cmd, args := Me().Prepend(Sudo()).Redefine("test")
34 | assert.Equal(t, "sudo", cmd)
35 | assert.Len(t, args, 1)
36 | cmd, args = Me().Prepend(NewCommandAsDifferentUser()).Redefine("test")
37 | assert.Equal(t, "test", cmd)
38 | assert.Empty(t, args)
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/utils/diodes/waiter.go:
--------------------------------------------------------------------------------
1 | package diodes
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | // Waiter will use a channel signal to alert the reader to when data is
8 | // available.
9 | type Waiter struct {
10 | Diode
11 | c chan struct{}
12 | ctx context.Context
13 | }
14 |
15 | // WaiterConfigOption can be used to setup the waiter.
16 | type WaiterConfigOption func(*Waiter)
17 |
18 | // WithWaiterContext sets the context to cancel any retrieval (Next()). It
19 | // will not change any results for adding data (Set()). Default is
20 | // context.Background().
21 | func WithWaiterContext(ctx context.Context) WaiterConfigOption {
22 | return WaiterConfigOption(func(c *Waiter) {
23 | c.ctx = ctx
24 | })
25 | }
26 |
27 | // NewWaiter returns a new Waiter that wraps the given diode.
28 | func NewWaiter(d Diode, opts ...WaiterConfigOption) *Waiter {
29 | w := new(Waiter)
30 | w.Diode = d
31 | w.c = make(chan struct{}, 1)
32 | w.ctx = context.Background()
33 |
34 | for _, opt := range opts {
35 | opt(w)
36 | }
37 |
38 | return w
39 | }
40 |
41 | // Set invokes the wrapped diode's Set with the given data and uses broadcast
42 | // to wake up any readers.
43 | func (w *Waiter) Set(data GenericDataType) {
44 | w.Diode.Set(data)
45 | w.broadcast()
46 | }
47 |
48 | // broadcast sends to the channel if it can.
49 | func (w *Waiter) broadcast() {
50 | select {
51 | case w.c <- struct{}{}:
52 | default:
53 | }
54 | }
55 |
56 | // Next returns the next data point on the wrapped diode. If there is no new
57 | // data, it will wait for Set to be called or the context to be done. If the
58 | // context is done, then nil will be returned.
59 | func (w *Waiter) Next() GenericDataType {
60 | for {
61 | data, ok := w.Diode.TryNext() // nolint:staticcheck
62 | if ok {
63 | return data
64 | }
65 | select {
66 | case <-w.ctx.Done():
67 | return nil
68 | case <-w.c:
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/utils/http/request_test.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/go-faker/faker/v4"
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 |
10 | "github.com/ARM-software/golang-utils/utils/field"
11 | )
12 |
13 | func TestRequestConfiguration_Validate(t *testing.T) {
14 | cfg := DefaultHTTPRequestConfiguration(faker.Name())
15 | require.Error(t, cfg.Validate())
16 | cfg.Host = faker.URL()
17 | require.NoError(t, cfg.Validate())
18 | cfg.Port = "123"
19 | require.NoError(t, cfg.Validate())
20 | cfg = DefaultHTTPRequestWithAuthorisationConfigurationEnforced(faker.Name())
21 | cfg.Host = faker.URL()
22 | require.Error(t, cfg.Validate())
23 | cfg.Authorisation.AccessToken = faker.Password()
24 | cfg.Authorisation.Scheme = faker.Name()
25 | require.Error(t, cfg.Validate())
26 | cfg.Authorisation.Scheme = AuthorisationSchemeToken
27 | require.NoError(t, cfg.Validate())
28 | }
29 |
30 | func TestAuth(t *testing.T) {
31 | cfg, err := NewAuthConfiguration(nil)
32 | require.NoError(t, err)
33 | assert.False(t, cfg.Enforced)
34 | cfg.Enforced = true
35 | require.Error(t, cfg.Validate())
36 | cfg.AccessToken = faker.Password()
37 | cfg.Scheme = faker.Name()
38 | require.Error(t, cfg.Validate())
39 | cfg.Scheme = AuthorisationSchemeToken
40 | require.NoError(t, cfg.Validate())
41 | cfg2, err := NewAuthConfiguration(field.ToOptionalString(faker.Password()))
42 | require.Error(t, err)
43 | assert.Empty(t, cfg2)
44 | cfg2, err = NewAuthConfiguration(field.ToOptionalString(cfg.GetAuthorizationHeader()))
45 | require.NoError(t, err)
46 | require.NoError(t, cfg2.Validate())
47 | assert.True(t, cfg.Enforced)
48 | cfg.Scheme = faker.Name()
49 | cfg2, err = NewAuthConfiguration(field.ToOptionalString(cfg.GetAuthorizationHeader()))
50 | require.Error(t, err)
51 | require.Error(t, cfg2.Validate())
52 | assert.True(t, cfg.Enforced)
53 | }
54 |
--------------------------------------------------------------------------------
/utils/logs/log_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package logs
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | "github.com/stretchr/testify/require"
12 | "go.uber.org/goleak"
13 |
14 | "github.com/ARM-software/golang-utils/utils/commonerrors"
15 | )
16 |
17 | func TestLog(t *testing.T) {
18 | defer goleak.VerifyNone(t)
19 | var loggers Loggers = &GenericLoggers{}
20 | err := loggers.Check()
21 | assert.Error(t, err)
22 | err = loggers.Close()
23 | assert.NoError(t, err)
24 | }
25 |
26 | func testLog(t *testing.T, loggers Loggers) {
27 | t.Helper()
28 | err := loggers.Check()
29 | require.NoError(t, err)
30 | defer func() { require.NoError(t, loggers.Close()) }()
31 |
32 | err = loggers.SetLogSource("source1")
33 | require.NoError(t, err)
34 | err = loggers.SetLoggerSource("LoggerSource1")
35 | require.NoError(t, err)
36 |
37 | loggers.Log("Test output1")
38 | loggers.Log("Test output2")
39 | loggers.Log("\"/usr/bin/armlink\" --via=\"/workspace/Objects/aws_mqtt_demo.axf._ld\"\n")
40 | loggers.Log("\n")
41 | loggers.LogError("\n")
42 | err = loggers.SetLogSource("source2")
43 | require.NoError(t, err)
44 |
45 | loggers.Log("Test output3")
46 | loggers.LogError("Test err1")
47 | err = loggers.SetLogSource("source3")
48 | require.NoError(t, err)
49 |
50 | err = loggers.SetLoggerSource("LoggerSource2")
51 | require.NoError(t, err)
52 |
53 | loggers.LogError("Test err2")
54 | err = loggers.SetLogSource("source4")
55 | require.NoError(t, err)
56 |
57 | loggers.LogError("Test err3")
58 | loggers.LogError(commonerrors.ErrCancelled)
59 | loggers.LogError(nil)
60 | loggers.LogError(commonerrors.ErrUnexpected, "some error")
61 | loggers.LogError("some error", commonerrors.ErrUnexpected)
62 | loggers.LogError(nil, "no error")
63 | }
64 |
--------------------------------------------------------------------------------
/utils/filesystem/files_unix_test.go:
--------------------------------------------------------------------------------
1 | //go:build !windows
2 |
3 | package filesystem
4 |
5 | import (
6 | "fmt"
7 | "net"
8 | "path/filepath"
9 | "testing"
10 |
11 | "github.com/go-faker/faker/v4"
12 | "github.com/stretchr/testify/assert"
13 | "github.com/stretchr/testify/require"
14 | "golang.org/x/sys/unix"
15 | )
16 |
17 | func TestIsUnixFile(t *testing.T) {
18 | for _, fsType := range FileSystemTypes {
19 | t.Run(fmt.Sprint(fsType), func(t *testing.T) {
20 | fs := NewFs(fsType)
21 |
22 | tmpDir := t.TempDir()
23 |
24 | t.Run("normal file", func(t *testing.T) {
25 | filePath := filepath.Join(tmpDir, faker.Word())
26 | err := fs.Touch(filePath)
27 | require.NoError(t, err)
28 | b, err := fs.IsFile(filePath)
29 | require.NoError(t, err)
30 | assert.True(t, b)
31 | })
32 |
33 | t.Run("special file", func(t *testing.T) {
34 | if fsType == InMemoryFS {
35 | t.Skip("In-memory file system won't have hardware devices or special files")
36 | }
37 |
38 | b, err := fs.IsFile("/dev/null")
39 | require.NoError(t, err)
40 | assert.True(t, b)
41 |
42 | fifoPath := filepath.Join(tmpDir, faker.Word())
43 | require.NoError(t, err)
44 | defer func() { _ = fs.Rm(fifoPath) }()
45 | err = unix.Mkfifo(fifoPath, 0666)
46 | require.NoError(t, err)
47 | b, err = fs.IsFile(fifoPath)
48 | require.NoError(t, err)
49 | assert.True(t, b)
50 | err = fs.Rm(fifoPath)
51 | require.NoError(t, err)
52 |
53 | socketPath := filepath.Join(tmpDir, faker.Word())
54 | require.NoError(t, err)
55 | defer func() { _ = fs.Rm(socketPath) }()
56 | l, err := net.Listen("unix", socketPath)
57 | require.NoError(t, err)
58 | defer func() { _ = l.Close() }()
59 | b, err = fs.IsFile(socketPath)
60 | require.NoError(t, err)
61 | assert.True(t, b)
62 | err = fs.Rm(socketPath)
63 | require.NoError(t, err)
64 | })
65 | })
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/utils/keyring/keyring_test.go:
--------------------------------------------------------------------------------
1 | package keyring
2 |
3 | import (
4 | "context"
5 | "testing"
6 | "time"
7 |
8 | "github.com/go-faker/faker/v4"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 |
12 | "github.com/ARM-software/golang-utils/utils/commonerrors"
13 | "github.com/ARM-software/golang-utils/utils/commonerrors/errortest"
14 | )
15 |
16 | type TestCfg struct {
17 | Test1 string
18 | Test2 int `mapstructure:"test64"`
19 | Test3 uint
20 | Test4 float64
21 | Test5 bool
22 | Test6 time.Duration
23 | Test7 time.Time
24 | Test8 time.Location
25 | }
26 |
27 | type TestCfg1 struct {
28 | Test1 string
29 | Test2 time.Duration
30 | Test3 TestCfg `mapstructure:"subtest_test"`
31 | }
32 |
33 | func TestKeyring(t *testing.T) {
34 | expected := TestCfg1{}
35 | require.NoError(t, faker.FakeData(&expected))
36 | prefix := faker.Word()
37 | err := Store[TestCfg1](context.Background(), prefix, &expected)
38 | errortest.AssertError(t, err, nil, commonerrors.ErrUnsupported)
39 | if commonerrors.Any(err, commonerrors.ErrUnsupported) {
40 | t.Skip("keyring is not supported")
41 | }
42 | actual := TestCfg1{}
43 | require.NoError(t, Fetch[TestCfg1](context.Background(), prefix, &actual))
44 | assert.EqualExportedValues(t, expected, actual)
45 | require.NoError(t, Clear(context.Background(), prefix))
46 | require.NoError(t, Fetch[TestCfg1](context.Background(), prefix, &actual))
47 | assert.EqualExportedValues(t, expected, actual)
48 | actual2 := TestCfg1{}
49 | require.NoError(t, Fetch[TestCfg1](context.Background(), prefix, &actual2))
50 | assert.Empty(t, actual2)
51 | t.Run("cancelled context", func(t *testing.T) {
52 | ctx, cancel := context.WithCancel(context.Background())
53 | cancel()
54 | errortest.AssertError(t, Fetch[TestCfg1](ctx, prefix, &actual), commonerrors.ErrCancelled)
55 | errortest.AssertError(t, Store[TestCfg1](ctx, prefix, &actual), commonerrors.ErrCancelled)
56 | })
57 | }
58 |
--------------------------------------------------------------------------------
/utils/mocks/mock_subprocess.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: github.com/ARM-software/golang-utils/utils/subprocess (interfaces: ICommandIO)
3 | //
4 | // Generated by this command:
5 | //
6 | // mockgen -destination=../mocks/mock_subprocess.go -package=mocks github.com/ARM-software/golang-utils/utils/subprocess ICommandIO
7 | //
8 |
9 | // Package mocks is a generated GoMock package.
10 | package mocks
11 |
12 | import (
13 | context "context"
14 | io "io"
15 | reflect "reflect"
16 |
17 | gomock "go.uber.org/mock/gomock"
18 | )
19 |
20 | // MockICommandIO is a mock of ICommandIO interface.
21 | type MockICommandIO struct {
22 | ctrl *gomock.Controller
23 | recorder *MockICommandIOMockRecorder
24 | isgomock struct{}
25 | }
26 |
27 | // MockICommandIOMockRecorder is the mock recorder for MockICommandIO.
28 | type MockICommandIOMockRecorder struct {
29 | mock *MockICommandIO
30 | }
31 |
32 | // NewMockICommandIO creates a new mock instance.
33 | func NewMockICommandIO(ctrl *gomock.Controller) *MockICommandIO {
34 | mock := &MockICommandIO{ctrl: ctrl}
35 | mock.recorder = &MockICommandIOMockRecorder{mock}
36 | return mock
37 | }
38 |
39 | // EXPECT returns an object that allows the caller to indicate expected use.
40 | func (m *MockICommandIO) EXPECT() *MockICommandIOMockRecorder {
41 | return m.recorder
42 | }
43 |
44 | // Register mocks base method.
45 | func (m *MockICommandIO) Register(arg0 context.Context) (io.Reader, io.Writer, io.Writer) {
46 | m.ctrl.T.Helper()
47 | ret := m.ctrl.Call(m, "Register", arg0)
48 | ret0, _ := ret[0].(io.Reader)
49 | ret1, _ := ret[1].(io.Writer)
50 | ret2, _ := ret[2].(io.Writer)
51 | return ret0, ret1, ret2
52 | }
53 |
54 | // Register indicates an expected call of Register.
55 | func (mr *MockICommandIOMockRecorder) Register(arg0 any) *gomock.Call {
56 | mr.mock.ctrl.T.Helper()
57 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockICommandIO)(nil).Register), arg0)
58 | }
59 |
--------------------------------------------------------------------------------
/utils/encryption/aesrsa/testhelpers/helper.go:
--------------------------------------------------------------------------------
1 | package testhelpers
2 |
3 | import (
4 | "bytes"
5 | "crypto/rand"
6 | "crypto/rsa"
7 | "crypto/x509"
8 | "crypto/x509/pkix"
9 | "encoding/pem"
10 | "math"
11 | "math/big"
12 | "path/filepath"
13 | "testing"
14 | "time"
15 |
16 | "github.com/go-faker/faker/v4"
17 | "github.com/stretchr/testify/require"
18 |
19 | "github.com/ARM-software/golang-utils/utils/filesystem"
20 | )
21 |
22 | func GenerateTestCerts(t *testing.T) (certPath, keyPath string) {
23 | t.Helper()
24 |
25 | key, err := rsa.GenerateKey(rand.Reader, 2048)
26 | require.NoError(t, err)
27 |
28 | serialNum, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
29 | require.NoError(t, err)
30 |
31 | certTemplate := &x509.Certificate{
32 | SerialNumber: serialNum,
33 | Subject: pkix.Name{Organization: []string{faker.Name()}}, //nolint:misspell // library is american
34 | NotBefore: time.Now(),
35 | NotAfter: time.Now().Add(1 * time.Minute),
36 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
37 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
38 | BasicConstraintsValid: true,
39 | }
40 |
41 | certB, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, &key.PublicKey, key)
42 | require.NoError(t, err)
43 |
44 | var certBuf, keyBuf bytes.Buffer
45 | err = pem.Encode(&certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: certB})
46 | require.NoError(t, err)
47 |
48 | err = pem.Encode(&keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
49 | require.NoError(t, err)
50 |
51 | tmpDir := t.TempDir()
52 |
53 | certPath = filepath.Join(tmpDir, "test_cert.pem")
54 | err = filesystem.WriteFile(certPath, certBuf.Bytes(), 0644)
55 | require.NoError(t, err)
56 |
57 | keyPath = filepath.Join(tmpDir, "test_key.pem")
58 | err = filesystem.WriteFile(keyPath, keyBuf.Bytes(), 0644)
59 | require.NoError(t, err)
60 |
61 | return
62 | }
63 |
--------------------------------------------------------------------------------
/utils/validation/rules.go:
--------------------------------------------------------------------------------
1 | package validation
2 |
3 | import (
4 | "reflect"
5 | "strconv"
6 |
7 | validation "github.com/go-ozzo/ozzo-validation/v4"
8 | "github.com/go-ozzo/ozzo-validation/v4/is"
9 |
10 | "github.com/ARM-software/golang-utils/utils/commonerrors"
11 | "github.com/ARM-software/golang-utils/utils/encoding/base64"
12 | "github.com/ARM-software/golang-utils/utils/url"
13 | )
14 |
15 | // IsPort validates whether a value is a port using is.Port from github.com/go-ozzo/ozzo-validation/v4.
16 | // However, it supports all base go integer types not just strings.
17 | var IsPort = validation.By(isPort)
18 |
19 | func isPort(vRaw any) (err error) {
20 | switch val := reflect.ValueOf(vRaw); val.Kind() {
21 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
22 | err = is.Port.Validate(strconv.FormatInt(val.Int(), 10))
23 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
24 | err = is.Port.Validate(strconv.FormatUint(val.Uint(), 10))
25 | case reflect.String:
26 | err = is.Port.Validate(val.String())
27 | case reflect.Slice:
28 | if b, ok := vRaw.([]byte); ok {
29 | err = is.Port.Validate(string(b))
30 | }
31 | default:
32 | return commonerrors.Newf(commonerrors.ErrMarshalling, "unsupported type for port validation '%T'", vRaw)
33 | }
34 |
35 | if err != nil {
36 | err = commonerrors.WrapError(commonerrors.ErrInvalid, err, "")
37 | return
38 | }
39 |
40 | return
41 | }
42 |
43 | // IsBase64 validates whether a value is a base64 encoded string. It is similar to is.Base64 but more generic and robust although less performant.
44 | var IsBase64 = validation.NewStringRuleWithError(base64.IsEncoded, is.ErrBase64)
45 |
46 | // IsPathParameter validates whether a value is a valid path parameter of a url.
47 | var IsPathParameter = validation.NewStringRule(isValidPathParameter, "invalid path parameter")
48 |
49 | func isValidPathParameter(value string) bool {
50 | err := url.ValidatePathParameter(value)
51 | return err == nil
52 | }
53 |
--------------------------------------------------------------------------------
/utils/diodes/poller.go:
--------------------------------------------------------------------------------
1 | package diodes
2 |
3 | import (
4 | "context"
5 | "time"
6 | )
7 |
8 | // Diode is any implementation of a diode.
9 | type Diode interface {
10 | Set(GenericDataType)
11 | TryNext() (GenericDataType, bool)
12 | }
13 |
14 | // Poller will poll a diode until a value is available.
15 | type Poller struct {
16 | Diode
17 | interval time.Duration
18 | ctx context.Context
19 | }
20 |
21 | // PollerConfigOption can be used to setup the poller.
22 | type PollerConfigOption func(*Poller)
23 |
24 | // WithPollingInterval sets the interval at which the diode is queried
25 | // for new data. The default is 10ms.
26 | func WithPollingInterval(interval time.Duration) PollerConfigOption {
27 | return PollerConfigOption(func(c *Poller) {
28 | c.interval = interval
29 | })
30 | }
31 |
32 | // WithPollingContext sets the context to cancel any retrieval (Next()). It
33 | // will not change any results for adding data (Set()). Default is
34 | // context.Background().
35 | func WithPollingContext(ctx context.Context) PollerConfigOption {
36 | return PollerConfigOption(func(c *Poller) {
37 | c.ctx = ctx
38 | })
39 | }
40 |
41 | // NewPoller returns a new Poller that wraps the given diode.
42 | func NewPoller(d Diode, opts ...PollerConfigOption) *Poller {
43 | p := &Poller{
44 | Diode: d,
45 | interval: 10 * time.Millisecond,
46 | ctx: context.Background(),
47 | }
48 |
49 | for _, o := range opts {
50 | o(p)
51 | }
52 |
53 | return p
54 | }
55 |
56 | // Next polls the diode until data is available or until the context is done.
57 | // If the context is done, then nil will be returned.
58 | func (p *Poller) Next() GenericDataType {
59 | for {
60 | data, ok := p.Diode.TryNext() // nolint:staticcheck
61 | if !ok {
62 | if p.IsDone() {
63 | return nil
64 | }
65 |
66 | time.Sleep(p.interval)
67 | continue
68 | }
69 | return data
70 | }
71 | }
72 |
73 | func (p *Poller) IsDone() bool {
74 | select {
75 | case <-p.ctx.Done():
76 | return true
77 | default:
78 | return false
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/utils/http/schemes/schemes.go:
--------------------------------------------------------------------------------
1 | package schemes
2 |
3 | const (
4 | AuthorisationSchemeToken = "Token"
5 | AuthorisationSchemeBasic = "Basic"
6 | AuthorisationSchemeBearer = "Bearer"
7 | AuthorisationSchemeConcealed = "Concealed"
8 | AuthorisationSchemeDigest = "Digest"
9 | AuthorisationSchemeDPoP = "DPoP"
10 | AuthorisationSchemeGNAP = "GNAP"
11 | AuthorisationSchemeHOBA = "HOBA"
12 | AuthorisationSchemeMutual = "Mutual"
13 | AuthorisationSchemeNegotiate = "Negotiate"
14 | AuthorisationSchemeOAuth = "OAuth"
15 | AuthorisationSchemePrivateToken = "PrivateToken"
16 | AuthorisationSchemeSCRAMSHA1 = "SCRAM-SHA-1"
17 | AuthorisationSchemeSCRAMSHA256 = "SCRAM-SHA-256"
18 | AuthorisationSchemeVapid = "vapid"
19 | )
20 |
21 | var (
22 | // HTTPAuthorisationSchemes lists all supported authorisation schemes. See https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml
23 | HTTPAuthorisationSchemes = []string{
24 | AuthorisationSchemeToken,
25 | AuthorisationSchemeBasic,
26 | AuthorisationSchemeBearer,
27 | AuthorisationSchemeConcealed,
28 | AuthorisationSchemeDigest,
29 | AuthorisationSchemeDPoP,
30 | AuthorisationSchemeGNAP,
31 | AuthorisationSchemeHOBA,
32 | AuthorisationSchemeMutual,
33 | AuthorisationSchemeNegotiate,
34 | AuthorisationSchemeOAuth,
35 | AuthorisationSchemePrivateToken,
36 | AuthorisationSchemeSCRAMSHA1,
37 | AuthorisationSchemeSCRAMSHA256,
38 | AuthorisationSchemeVapid,
39 | }
40 | InAuthSchemes = []any{
41 | AuthorisationSchemeToken,
42 | AuthorisationSchemeBasic,
43 | AuthorisationSchemeBearer,
44 | AuthorisationSchemeConcealed,
45 | AuthorisationSchemeDigest,
46 | AuthorisationSchemeDPoP,
47 | AuthorisationSchemeGNAP,
48 | AuthorisationSchemeHOBA,
49 | AuthorisationSchemeMutual,
50 | AuthorisationSchemeNegotiate,
51 | AuthorisationSchemeOAuth,
52 | AuthorisationSchemePrivateToken,
53 | AuthorisationSchemeSCRAMSHA1,
54 | AuthorisationSchemeSCRAMSHA256,
55 | AuthorisationSchemeVapid,
56 | }
57 | )
58 |
--------------------------------------------------------------------------------
/utils/safeio/read_closer_test.go:
--------------------------------------------------------------------------------
1 | package safeio
2 |
3 | import (
4 | "context"
5 | "io"
6 | "os"
7 | "testing"
8 | "time"
9 |
10 | "github.com/stretchr/testify/assert"
11 | "github.com/stretchr/testify/require"
12 | "go.uber.org/goleak"
13 | )
14 |
15 | func TestNewContextualReadCloser(t *testing.T) {
16 | t.Run("Normal contextual reader blocks even after cancel", func(t *testing.T) {
17 | defer goleak.VerifyNone(t)
18 |
19 | r, w, err := os.Pipe()
20 | require.NoError(t, err)
21 | defer func() { _ = r.Close(); _ = w.Close() }()
22 |
23 | ctx, cancel := context.WithCancel(context.Background())
24 | reader := NewContextualReader(ctx, r)
25 |
26 | done := make(chan struct{})
27 | go func() {
28 | _, _ = io.Copy(io.Discard, reader) // will block in read(2) https://man7.org/linux/man-pages/man2/read.2.html
29 | close(done)
30 | }()
31 |
32 | // Allow io.Copy to enter kernel read then try to cancel
33 | time.Sleep(50 * time.Millisecond)
34 | cancel()
35 |
36 | select {
37 | case <-done:
38 | assert.FailNow(t, "cancelling context shouldn't unblock a blocking Read in io.Copy")
39 | case <-time.After(200 * time.Millisecond):
40 | // Expected case: still blocked
41 | }
42 | })
43 |
44 | t.Run("Contextual read closer does not block even on long running copies", func(t *testing.T) {
45 | defer goleak.VerifyNone(t)
46 |
47 | r, w, err := os.Pipe()
48 | require.NoError(t, err)
49 | defer func() { _ = w.Close() }()
50 |
51 | ctx, cancel := context.WithCancel(context.Background())
52 | rc := NewContextualReadCloser(ctx, r)
53 |
54 | done := make(chan struct{})
55 | go func() {
56 | _, _ = io.Copy(io.Discard, rc) // will block in read(2) https://man7.org/linux/man-pages/man2/read.2.html
57 | close(done)
58 | }()
59 |
60 | time.Sleep(50 * time.Millisecond)
61 | cancel()
62 |
63 | select {
64 | case <-done:
65 | // Expected case: successfully unblocked
66 | case <-time.After(2 * time.Second):
67 | assert.FailNow(t, "copy should have been unblocked by context cancel")
68 | }
69 | })
70 | }
71 |
--------------------------------------------------------------------------------
/utils/filesystem/filehash.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package filesystem
6 |
7 | import (
8 | "context"
9 |
10 | "github.com/ARM-software/golang-utils/utils/commonerrors"
11 | "github.com/ARM-software/golang-utils/utils/hashing"
12 | )
13 |
14 | type fileHashing struct {
15 | algo hashing.IHash
16 | }
17 |
18 | func (h *fileHashing) CalculateWithContext(ctx context.Context, f File) (string, error) {
19 | if f == nil {
20 | return "", commonerrors.ErrUndefined
21 | }
22 | return h.algo.CalculateWithContext(ctx, f)
23 | }
24 |
25 | func (h *fileHashing) Calculate(f File) (string, error) {
26 | if f == nil {
27 | return "", commonerrors.ErrUndefined
28 | }
29 | return h.algo.Calculate(f)
30 | }
31 |
32 | func (h *fileHashing) GetType() string {
33 | return h.algo.GetType()
34 | }
35 |
36 | func (h *fileHashing) CalculateFileWithContext(ctx context.Context, fs FS, path string) (string, error) {
37 | return h.calculateFile(fs, path, func(m *fileHashing, f File) (string, error) { return m.CalculateWithContext(ctx, f) })
38 | }
39 |
40 | func (h *fileHashing) CalculateFile(fs FS, path string) (string, error) {
41 | return h.calculateFile(fs, path, func(m *fileHashing, f File) (string, error) { return m.Calculate(f) })
42 | }
43 |
44 | func (h *fileHashing) calculateFile(fs FS, path string, hashFunc func(h *fileHashing, f File) (string, error)) (string, error) {
45 | ok, err := fs.IsFile(path)
46 | if err != nil || !ok {
47 | if err != nil {
48 | return "", err
49 | }
50 | err = commonerrors.Newf(commonerrors.ErrInvalid, "not a file [%v]", path)
51 | return "", err
52 | }
53 | f, err := fs.GenericOpen(path)
54 | if err != nil {
55 | return "", err
56 | }
57 | defer func() { _ = f.Close() }()
58 | return hashFunc(h, f)
59 | }
60 |
61 | func NewFileHash(hashType string) (IFileHash, error) {
62 | algo, err := hashing.NewHashingAlgorithm(hashType)
63 | if err != nil {
64 | return nil, err
65 | }
66 | return &fileHashing{
67 | algo: algo,
68 | }, nil
69 | }
70 |
--------------------------------------------------------------------------------
/utils/config/validation.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package config
6 |
7 | import (
8 | "reflect"
9 | "strings"
10 |
11 | "github.com/ARM-software/golang-utils/utils/collection"
12 | fieldUtils "github.com/ARM-software/golang-utils/utils/field"
13 | )
14 |
15 | var specialMapstructureTags = []string{"squash", "remain", "omitempty", "omitzero"} // See https://pkg.go.dev/github.com/go-viper/mapstructure/v2#section-readme
16 |
17 | // ValidateEmbedded uses reflection to find embedded structs and validate them
18 | func ValidateEmbedded(cfg Validator) error {
19 | r := reflect.ValueOf(cfg).Elem()
20 | for i := 0; i < r.NumField(); i++ {
21 | f := r.Field(i)
22 | if f.Kind() == reflect.Struct {
23 | validator, ok := f.Addr().Interface().(Validator)
24 | if !ok {
25 | continue
26 | }
27 | err := validator.Validate()
28 | field := r.Type().Field(i)
29 |
30 | err = wrapFieldValidationError(field, err)
31 | if err != nil {
32 | return err
33 | }
34 | }
35 | }
36 | return nil
37 | }
38 |
39 | func wrapFieldValidationError(field reflect.StructField, err error) error {
40 | mapStructureStr, hasTag := field.Tag.Lookup("mapstructure")
41 | mapStructure := fieldUtils.ToOptionalStringOrNilIfEmpty(processMapStructureString(mapStructureStr))
42 | if !hasTag {
43 | mapStructure = nil
44 | }
45 | err = WrapFieldValidationError(field.Name, mapStructure, nil, err)
46 | return err
47 | }
48 |
49 | // mapstructure has some special tags which need to be accounted for.
50 | func processMapStructureString(str string) string {
51 | processedStr := strings.TrimSpace(str)
52 | if processedStr == "-" {
53 | return ""
54 | }
55 |
56 | elements := strings.Split(processedStr, ",")
57 | if len(elements) == 1 {
58 | return processedStr
59 | }
60 | elements = collection.GenericRemove(func(str1, str2 string) bool {
61 | return strings.EqualFold(strings.TrimSpace(str1), strings.TrimSpace(str2))
62 | }, elements, specialMapstructureTags...)
63 | return strings.TrimSpace(strings.Join(elements, ","))
64 | }
65 |
--------------------------------------------------------------------------------
/utils/logs/interfaces.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | // Package logs defines loggers for use in projects.
7 | package logs
8 |
9 | import (
10 | "io"
11 |
12 | "github.com/go-logr/logr"
13 | )
14 |
15 | //go:generate go tool mockgen -destination=../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/$GOPACKAGE Loggers,IMultipleLoggers,WriterWithSource,StdLogger
16 |
17 | // Loggers defines generic loggers which separate common logging messages from errors.
18 | // This is to use in cases where it is necessary to separate the two streams e.g. remote procedure call (RPC)
19 | // In most cases however, if only a standard logger is needed, it is advised to use logr.Logger.
20 | type Loggers interface {
21 | io.Closer
22 | // Check returns whether the loggers are correctly defined or not.
23 | Check() error
24 | // SetLogSource sets the source of the log message e.g. related build job, related command, etc.
25 | SetLogSource(source string) error
26 | // SetLoggerSource sets the source of the logger e.g. APIs, Build worker, CMSIS tools.
27 | SetLoggerSource(source string) error
28 | // Log logs to the output stream/logger.
29 | Log(output ...interface{})
30 | // LogError logs to the Error stream/logger.
31 | LogError(err ...interface{})
32 | }
33 |
34 | // IMultipleLoggers provides an interface to manage multiple loggers the same way as a single logger.
35 | type IMultipleLoggers interface {
36 | Loggers
37 | // AppendLogger appends generic loggers to the internal list of loggers managed by this system.
38 | AppendLogger(l ...logr.Logger) error
39 | // Append appends loggers to the internal list of loggers managed by this system.
40 | Append(l ...Loggers) error
41 | }
42 |
43 | type WriterWithSource interface {
44 | io.WriteCloser
45 | SetSource(source string) error
46 | }
47 |
48 | // StdLogger is the subset of the Go stdlib log.Logger API.
49 | type StdLogger interface {
50 | // Output is the same as log.Output and log.Logger.Output.
51 | Output(calldepth int, logline string) error
52 | }
53 |
--------------------------------------------------------------------------------
/utils/subprocess/io.go:
--------------------------------------------------------------------------------
1 | package subprocess
2 |
3 | import (
4 | "context"
5 | "io"
6 | "os"
7 | "sync"
8 |
9 | "github.com/ARM-software/golang-utils/utils/logs"
10 | )
11 |
12 | //go:generate go tool mockgen -destination=../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/$GOPACKAGE ICommandIO
13 |
14 | // ICommandIO allows you to set the stdin, stdout, and stderr that will be used in a subprocess. A context can be injected for context aware readers and writers
15 | type ICommandIO interface {
16 | // Register creates new readers and writers based on the constructor methods in the ICommandIO implementation. If the constructors are not specified then it will default to os.Stdin, os.Stdout, and os.Stderr
17 | Register(context.Context) (in io.Reader, out, errs io.Writer)
18 | }
19 |
20 | type commandIO struct {
21 | newInFunc func(context.Context) io.Reader
22 | newOutFunc func(context.Context) io.Writer
23 | newErrorFunc func(context.Context) io.Writer
24 | mu sync.Mutex
25 | }
26 |
27 | func NewIO(
28 | newInFunc func(context.Context) io.Reader,
29 | newOutFunc func(context.Context) io.Writer,
30 | newErrorFunc func(context.Context) io.Writer,
31 | ) ICommandIO {
32 | return &commandIO{
33 | mu: sync.Mutex{},
34 | newInFunc: newInFunc,
35 | newOutFunc: newOutFunc,
36 | newErrorFunc: newErrorFunc,
37 | }
38 | }
39 |
40 | func NewIOFromLoggers(loggers logs.Loggers) ICommandIO {
41 | return NewIO(
42 | nil,
43 | func(ctx context.Context) io.Writer { return newOutStreamer(ctx, loggers) },
44 | func(ctx context.Context) io.Writer { return newErrLogStreamer(ctx, loggers) },
45 | )
46 | }
47 |
48 | func NewDefaultIO() ICommandIO {
49 | return NewIO(nil, nil, nil)
50 | }
51 |
52 | func (c *commandIO) Register(ctx context.Context) (in io.Reader, out, errs io.Writer) {
53 | c.mu.Lock()
54 | defer c.mu.Unlock()
55 | in, out, errs = os.Stdin, os.Stdout, os.Stderr
56 | if c.newInFunc != nil {
57 | in = c.newInFunc(ctx)
58 | }
59 | if c.newOutFunc != nil {
60 | out = c.newOutFunc(ctx)
61 | }
62 | if c.newErrorFunc != nil {
63 | errs = c.newErrorFunc(ctx)
64 | }
65 | return
66 | }
67 |
--------------------------------------------------------------------------------
/utils/cache/filecache/lockmap.go:
--------------------------------------------------------------------------------
1 | package filecache
2 |
3 | import "sync"
4 |
5 | type iLockMap interface {
6 | Lock(key string)
7 | TryLock(key string) bool
8 | Unlock(key string)
9 | Store(key string)
10 | Range(f func(key string, mu *sync.Mutex) bool)
11 | Delete(key string)
12 | Clear()
13 | }
14 |
15 | type lockMap struct {
16 | // locks holds a map of per-key mutexes.
17 | //
18 | // We use sync.Map instead of a plain map + RWMutex to optimise for:
19 | //
20 | // 1. **Write-once, read-many**
21 | // Each key’s mutex is created exactly once (when a cache entry is created),
22 | // then read repeatedly for the rest of the cache operations.
23 | //
24 | // 2. **Disjoint key access**
25 | // Goroutines operate on different keys independently most of time,
26 | // as they work on differenct resources.
27 | // sync.Map will significantly reduce global lock contention
28 | // when multiple goroutines run concurrent cache operations on different files.
29 | //
30 | // See https://pkg.go.dev/sync#Map for details on these optimisations.
31 | locks sync.Map
32 | }
33 |
34 | func (lm *lockMap) Lock(key string) {
35 | if mu := lm.load(key); mu != nil {
36 | mu.Lock()
37 | }
38 | }
39 |
40 | func (lm *lockMap) TryLock(key string) bool {
41 | mu := lm.load(key)
42 | return mu != nil && mu.TryLock()
43 | }
44 |
45 | func (lm *lockMap) Unlock(key string) {
46 | if mu := lm.load(key); mu != nil {
47 | mu.Unlock()
48 | }
49 | }
50 |
51 | func (lm *lockMap) load(key string) *sync.Mutex {
52 | mu, exists := lm.locks.Load(key)
53 |
54 | if !exists {
55 | return nil
56 | }
57 |
58 | return mu.(*sync.Mutex)
59 | }
60 |
61 | func (lm *lockMap) Store(key string) {
62 | lm.locks.Store(key, &sync.Mutex{})
63 | }
64 |
65 | func (lm *lockMap) Delete(key string) {
66 | lm.locks.Delete(key)
67 | }
68 |
69 | func (lm *lockMap) Clear() {
70 | lm.locks.Clear()
71 | }
72 |
73 | func (lm *lockMap) Range(f func(key string, mu *sync.Mutex) bool) {
74 | lm.locks.Range(func(k, v any) bool {
75 | keyStr := k.(string)
76 | mutex := v.(*sync.Mutex)
77 | return f(keyStr, mutex)
78 | })
79 | }
80 |
81 | func newLockMap() *lockMap {
82 | return &lockMap{}
83 | }
84 |
--------------------------------------------------------------------------------
/utils/retry/retry.go:
--------------------------------------------------------------------------------
1 | package retry
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/avast/retry-go/v4"
9 | "github.com/go-logr/logr"
10 |
11 | "github.com/ARM-software/golang-utils/utils/commonerrors"
12 | "github.com/ARM-software/golang-utils/utils/safecast"
13 | )
14 |
15 | // RetryIf will retry fn when the value returned from retryConditionFn is true
16 | func RetryIf(ctx context.Context, logger logr.Logger, retryPolicy *RetryPolicyConfiguration, fn func() error, msgOnRetry string, retryConditionFn func(err error) bool) error {
17 | if retryPolicy == nil {
18 | return commonerrors.New(commonerrors.ErrUndefined, "missing retry policy configuration")
19 | }
20 | if !retryPolicy.Enabled {
21 | return fn()
22 | }
23 | var retryType retry.DelayTypeFunc
24 | switch {
25 | case retryPolicy.LinearBackOffEnabled:
26 | retryType = retry.CombineDelay(retry.FixedDelay, retry.RandomDelay)
27 | case retryPolicy.BackOffEnabled:
28 | retryType = retry.BackOffDelay
29 | default:
30 | retryType = retry.FixedDelay
31 | }
32 |
33 | return commonerrors.ConvertContextError(
34 | retry.Do(
35 | fn,
36 | retry.OnRetry(func(n uint, err error) {
37 | logger.Error(err, fmt.Sprintf("%v (attempt #%v)", msgOnRetry, n+1), "attempt", n+1)
38 | }),
39 | retry.Delay(retryPolicy.RetryWaitMin),
40 | retry.MaxDelay(retryPolicy.RetryWaitMax),
41 | retry.MaxJitter(25*time.Millisecond),
42 | retry.DelayType(retryType),
43 | retry.Attempts(safecast.ToUint(retryPolicy.RetryMax)),
44 | retry.RetryIf(retryConditionFn),
45 | retry.LastErrorOnly(true),
46 | retry.Context(ctx),
47 | ),
48 | )
49 | }
50 |
51 | // RetryOnError allows the caller to retry fn when the error returned by fn is retriable
52 | // as in of the type specified by retriableErr. backoff defines the maximum retries and the wait
53 | // interval between two retries.
54 | func RetryOnError(ctx context.Context, logger logr.Logger, retryPolicy *RetryPolicyConfiguration, fn func() error, msgOnRetry string, retriableErr ...error) error {
55 | return RetryIf(ctx, logger, retryPolicy, fn, msgOnRetry, func(err error) bool {
56 | return commonerrors.Any(err, retriableErr...)
57 | })
58 | }
59 |
--------------------------------------------------------------------------------
/utils/maps/map.go:
--------------------------------------------------------------------------------
1 | package maps
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | const separator = "."
8 |
9 | // Map is a wrapper around maps[string]string that provides some helpers
10 | // above it that assume the maps is in the format that flatmap expects
11 | // (the result of Flatten).
12 | //
13 | // All modifying functions such as Delete are done in-place unless
14 | // otherwise noted.
15 | type Map map[string]string
16 |
17 | // Contains returns true if the maps contains the given key.
18 | func (m Map) Contains(key string) bool {
19 | for _, k := range m.Keys() {
20 | if k == key {
21 | return true
22 | }
23 | }
24 |
25 | return false
26 | }
27 |
28 | func (m Map) AsMap() map[string]string {
29 | return m
30 | }
31 |
32 | // Delete deletes a key out of the maps with the given prefix.
33 | func (m Map) Delete(prefix string) {
34 | for k := range m {
35 | match := k == prefix
36 | if !match {
37 | if !strings.HasPrefix(k, prefix) {
38 | continue
39 | }
40 |
41 | if k[len(prefix):len(prefix)+1] != separator {
42 | continue
43 | }
44 | }
45 |
46 | delete(m, k)
47 | }
48 | }
49 |
50 | // Keys returns all the top-level keys in this maps
51 | func (m Map) Keys() []string {
52 | ks := make(map[string]struct{})
53 | for k := range m {
54 | idx := strings.Index(k, separator)
55 | if idx == -1 {
56 | idx = len(k)
57 | }
58 |
59 | ks[k[:idx]] = struct{}{}
60 | }
61 |
62 | result := make([]string, 0, len(ks))
63 | for k := range ks {
64 | result = append(result, k)
65 | }
66 |
67 | return result
68 | }
69 |
70 | // Merge merges the contents of the other Map into this one.
71 | //
72 | // This merge is smarter than a simple maps iteration because it
73 | // will fully replace arrays and other complex structures that
74 | // are present in this maps with the other maps's. For example, if
75 | // this maps has a 3 element "foo" list, and m2 has a 2 element "foo"
76 | // list, then the result will be that m has a 2 element "foo"
77 | // list.
78 | func (m Map) Merge(m2 Map) {
79 | for _, prefix := range m2.Keys() {
80 | m.Delete(prefix)
81 |
82 | for k, v := range m2 {
83 | if strings.HasPrefix(k, prefix) {
84 | m[k] = v
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/utils/proc/find/find_test.go:
--------------------------------------------------------------------------------
1 | package find
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os/exec"
7 | "runtime"
8 | "testing"
9 | "time"
10 |
11 | "github.com/stretchr/testify/assert"
12 | "github.com/stretchr/testify/require"
13 | "go.uber.org/goleak"
14 |
15 | "github.com/ARM-software/golang-utils/utils/commonerrors"
16 | "github.com/ARM-software/golang-utils/utils/commonerrors/errortest"
17 | )
18 |
19 | func TestFindProcessByName(t *testing.T) {
20 | if runtime.GOOS != "linux" {
21 | defer goleak.VerifyNone(t)
22 | }
23 | tests := []struct {
24 | cmdWindows *exec.Cmd
25 | cmdOther *exec.Cmd
26 | processNameWindows string
27 | processNameOther string
28 | }{
29 | {
30 | cmdWindows: exec.Command("cmd.exe", "/c", fmt.Sprintf("ping localhost -n %v > nul", time.Second.Seconds())), //nolint: gosec // G204 Subprocess launched with a potential tainted input or cmd arguments (gosec)
31 | cmdOther: exec.Command("sh", "-c", fmt.Sprintf("sleep %v", time.Second.Seconds())), //nolint: gosec // G204 Subprocess launched with a potential tainted input or cmd arguments (gosec)
32 | processNameWindows: "ping",
33 | processNameOther: "sleep",
34 | },
35 | }
36 |
37 | for i := range tests {
38 | test := tests[i]
39 | t.Run("subtest", func(t *testing.T) {
40 | ctx := context.Background()
41 | cmd := test.cmdOther
42 | toFind := test.processNameOther
43 | if runtime.GOOS == "windows" {
44 | cmd = test.cmdWindows
45 | toFind = test.processNameWindows
46 | }
47 | ps, err := FindProcessByName(ctx, toFind)
48 | require.NoError(t, err)
49 | numOfProcesses := len(ps)
50 | require.NoError(t, cmd.Start())
51 | defer func() { _ = cmd.Process.Kill() }()
52 | ps, err = FindProcessByName(ctx, toFind)
53 | require.NoError(t, err)
54 | assert.NotEmpty(t, ps)
55 |
56 | assert.GreaterOrEqual(t, len(ps), numOfProcesses)
57 | require.NoError(t, cmd.Wait())
58 | t.Run("cancelled context", func(t *testing.T) {
59 | cancelCtx, cancel := context.WithCancel(ctx)
60 | cancel()
61 | _, err = FindProcessByName(cancelCtx, toFind)
62 | errortest.AssertError(t, err, commonerrors.ErrCancelled)
63 | })
64 | })
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/utils/serialization/xml_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 | package serialization //nolint:misspell
6 |
7 | import (
8 | "path"
9 | "testing"
10 |
11 | "github.com/stretchr/testify/assert"
12 | "github.com/stretchr/testify/require"
13 |
14 | "github.com/ARM-software/golang-utils/utils/filesystem"
15 | )
16 |
17 | func TestUnmarshalAttr(t *testing.T) {
18 | type ParamVal struct {
19 | Int int `xml:"int,attr"`
20 | }
21 |
22 | type ParamPtr struct {
23 | Int *int `xml:"int,attr"`
24 | }
25 |
26 | type ParamStringPtr struct {
27 | Int *string `xml:"int,attr"`
28 | }
29 |
30 | x := []byte(``)
31 |
32 | paramPtr := &ParamPtr{}
33 | err := UnmarshallXML(x, paramPtr)
34 | assert.Nil(t, err)
35 | assert.Equal(t, 1, *paramPtr.Int)
36 |
37 | paramVal := &ParamVal{}
38 | err = UnmarshallXML(x, paramVal)
39 | assert.Nil(t, err)
40 | assert.Equal(t, 1, paramVal.Int)
41 |
42 | paramStrPtr := &ParamStringPtr{}
43 | err = UnmarshallXML(x, paramStrPtr)
44 | assert.Nil(t, err)
45 | assert.Equal(t, "1", *paramStrPtr.Int)
46 | }
47 |
48 | func TestUnmarshalUTF8(t *testing.T) {
49 | type TestStruct struct {
50 | Attr string `xml:",attr"`
51 | }
52 |
53 | const inputData = `
54 |
55 | `
56 |
57 | expected := "Hello, 世界"
58 |
59 | var x TestStruct
60 | err := UnmarshallXML([]byte(inputData), &x)
61 | assert.Nil(t, err)
62 | assert.Equal(t, expected, x.Attr)
63 | }
64 |
65 | func TestUnmarshal_UTF8_GB2312(t *testing.T) {
66 | type Pack struct {
67 | URL string
68 | DataChn string
69 | Name string `xml:",attr"`
70 | Vendor string `xml:",attr"`
71 | }
72 |
73 | var pack Pack
74 | byteValue, err := filesystem.ReadFile(path.Join("testdata", "testfile_GB2312.xml"))
75 | require.Nil(t, err)
76 | require.Nil(t, UnmarshallXML(byteValue, &pack))
77 |
78 | bytes := []byte(pack.DataChn)
79 | expectedbytes := []byte("世界")
80 |
81 | assert.Equal(t, expectedbytes, bytes)
82 | assert.Equal(t, "ARM", pack.Vendor)
83 | assert.Equal(t, "CMSIS", pack.Name)
84 | assert.Equal(t, "http://www.keil.com/pack/", pack.URL)
85 | }
86 |
--------------------------------------------------------------------------------
/utils/cache/filecache/entrymap.go:
--------------------------------------------------------------------------------
1 | package filecache
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | type iEntryMap interface {
8 | Load(key string) ICacheEntry
9 | Store(key string, entry ICacheEntry)
10 | Range(f func(key string, entry ICacheEntry))
11 | Exists(key string) bool
12 | Delete(key string)
13 | Clear()
14 | }
15 | type entryMap struct {
16 | // entries holds a map of per-key cache entry.
17 | //
18 | // We choose sync.Map over a plain map + RWMutex to leverage its optimised
19 | // two-phase Range iteration, which dramatically reduces blocking during map iterations,
20 | // avoiding a global lock on the whole iteration, and only lock for the few entries that actually changed
21 | //
22 | // 1. **Read-only “clean” phase**
23 | // Range first iterates over a snapshot of existing entries without any locks,
24 | // letting goroutines continue Store/Delete concurrently.
25 | //
26 | // 2. **Locked “dirty” phase**
27 | // Any entries added or removed during the clean pass are tracked in a small
28 | // “dirty” buffer. Range then locks only this buffer to apply your callback
29 | // to those changed entries.
30 | //
31 | // This optimisation greatly reduces global locking during the cache's garbage collection runtime
32 | //
33 | // See https://pkg.go.dev/sync#Map.Range for details on this optimisation
34 | entries sync.Map
35 | }
36 |
37 | func (em *entryMap) Load(key string) ICacheEntry {
38 | entry, exists := em.entries.Load(key)
39 | if !exists {
40 | return nil
41 | }
42 |
43 | return entry.(ICacheEntry)
44 | }
45 |
46 | func (em *entryMap) Exists(key string) bool {
47 | _, exists := em.entries.Load(key)
48 | return exists
49 | }
50 |
51 | func (em *entryMap) Store(key string, entry ICacheEntry) {
52 | em.entries.Store(key, entry)
53 | }
54 |
55 | func (em *entryMap) Delete(key string) {
56 | em.entries.Delete(key)
57 | }
58 |
59 | func (em *entryMap) Clear() {
60 | em.entries.Clear()
61 | }
62 |
63 | func (em *entryMap) Range(f func(key string, entry ICacheEntry)) {
64 | em.entries.Range(func(k, v any) bool {
65 | keyStr := k.(string)
66 | cacheEntry := v.(ICacheEntry)
67 | f(keyStr, cacheEntry)
68 | return true
69 | })
70 | }
71 |
72 | func newEntryMap() *entryMap {
73 | return &entryMap{}
74 | }
75 |
--------------------------------------------------------------------------------
/utils/charset/iconv/converter.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
3 | * SPDX-License-Identifier: Apache-2.0
4 | */
5 |
6 | package iconv
7 |
8 | import (
9 | "bufio"
10 | "bytes"
11 | "context"
12 | "fmt"
13 | "io"
14 |
15 | "golang.org/x/text/encoding"
16 | "golang.org/x/text/encoding/unicode"
17 | "golang.org/x/text/transform"
18 |
19 | "github.com/ARM-software/golang-utils/utils/safeio"
20 | )
21 |
22 | // This is really similar to https://github.com/mushroomsir/iconv
23 |
24 | func NewConverter(fromEncoding encoding.Encoding, toEncoding encoding.Encoding) ICharsetConverter {
25 | return &Converter{
26 | fromEncoding: fromEncoding,
27 | toEncoding: toEncoding,
28 | }
29 | }
30 |
31 | type Converter struct {
32 | fromEncoding encoding.Encoding
33 | toEncoding encoding.Encoding
34 | }
35 |
36 | func (t *Converter) ConvertStringWithContext(ctx context.Context, input string) (transformedStr string, err error) {
37 | res, err := t.ConvertBytesWithContext(ctx, []byte(input))
38 | if err != nil {
39 | return
40 | }
41 | transformedStr = string(res)
42 | return
43 | }
44 |
45 | func (t *Converter) ConvertBytesWithContext(ctx context.Context, input []byte) ([]byte, error) {
46 | reader := t.Convert(bytes.NewReader(input))
47 | return safeio.ReadAll(ctx, reader)
48 | }
49 |
50 | func (t *Converter) ConvertString(input string) (string, error) {
51 | return t.ConvertStringWithContext(context.Background(), input)
52 | }
53 |
54 | func (t *Converter) ConvertBytes(input []byte) ([]byte, error) {
55 | return t.ConvertBytesWithContext(context.Background(), input)
56 | }
57 |
58 | func (t *Converter) Convert(reader io.Reader) io.Reader {
59 | return t.convert(reader)
60 | }
61 |
62 | func (t *Converter) String() string {
63 | return fmt.Sprintf("%v to %v", t.fromEncoding, t.toEncoding)
64 | }
65 |
66 | func (t *Converter) convert(reader io.Reader) (resReader io.Reader) {
67 | if t.fromEncoding == unicode.UTF8 {
68 | resReader = bufio.NewReader(reader)
69 | } else {
70 | resReader = transform.NewReader(reader, t.fromEncoding.NewDecoder())
71 | }
72 | if t.toEncoding != unicode.UTF8 {
73 | resReader = transform.NewReader(resReader, t.toEncoding.NewEncoder())
74 | }
75 | return
76 | }
77 |
--------------------------------------------------------------------------------
/utils/platform/users_windows_arm64.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 |
3 | package platform
4 |
5 | import (
6 | "context"
7 | "fmt"
8 |
9 | "github.com/ARM-software/golang-utils/utils/commonerrors"
10 | "github.com/ARM-software/golang-utils/utils/parallelisation"
11 | )
12 |
13 | // Note : there is no implementation for the arm architecture until support is provided by the following library https://github.com/iamacarpet/go-win64api/issues/62
14 |
15 | func addUser(ctx context.Context, username, fullname, password string) (err error) {
16 | err = parallelisation.DetermineContextError(ctx)
17 | if err != nil {
18 | return
19 | }
20 | err = fmt.Errorf("%w: cannot add user", commonerrors.ErrNotImplemented)
21 | return
22 | }
23 |
24 | func removeUser(ctx context.Context, username string) (err error) {
25 | err = parallelisation.DetermineContextError(ctx)
26 | if err != nil {
27 | return
28 | }
29 | err = fmt.Errorf("%w: cannot remove user", commonerrors.ErrNotImplemented)
30 | return
31 | }
32 |
33 | func addGroup(ctx context.Context, groupName string) (err error) {
34 | err = parallelisation.DetermineContextError(ctx)
35 | if err != nil {
36 | return
37 | }
38 | err = fmt.Errorf("%w: cannot add group", commonerrors.ErrNotImplemented)
39 | return
40 | }
41 |
42 | func associateUserToGroup(ctx context.Context, username, groupName string) (err error) {
43 | err = parallelisation.DetermineContextError(ctx)
44 | if err != nil {
45 | return
46 | }
47 | err = fmt.Errorf("%w: cannot associate user to group", commonerrors.ErrNotImplemented)
48 | return
49 | }
50 |
51 | func dissociateUserFromGroup(ctx context.Context, username, groupName string) (err error) {
52 | err = parallelisation.DetermineContextError(ctx)
53 | if err != nil {
54 | return
55 | }
56 | err = fmt.Errorf("%w: cannot dissociate user from group", commonerrors.ErrNotImplemented)
57 | return
58 | }
59 |
60 | func removeGroup(ctx context.Context, groupName string) (err error) {
61 | err = parallelisation.DetermineContextError(ctx)
62 | if err != nil {
63 | return
64 | }
65 | err = fmt.Errorf("%w: cannot associate user to group", commonerrors.ErrNotImplemented)
66 | return
67 | }
68 |
69 | func isAdmin(username string) (admin bool, err error) {
70 | err = fmt.Errorf("%w: cannot determine if user is admin", commonerrors.ErrNotImplemented)
71 | return
72 | }
73 |
--------------------------------------------------------------------------------