├── .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 | --------------------------------------------------------------------------------