From 8910c5a7c60e9100e62beebbdea7a3aded1a54ec Mon Sep 17 00:00:00 2001 From: Leonardo Cecchi Date: Wed, 20 Nov 2024 11:13:06 +0100 Subject: [PATCH] feat: add envmap package Signed-off-by: Leonardo Cecchi --- pkg/envmap/doc.go | 20 ++++ pkg/envmap/envutils.go | 91 ++++++++++++++++++ pkg/envmap/envutils_test.go | 185 ++++++++++++++++++++++++++++++++++++ pkg/envmap/suite_test.go | 33 +++++++ 4 files changed, 329 insertions(+) create mode 100644 pkg/envmap/doc.go create mode 100644 pkg/envmap/envutils.go create mode 100644 pkg/envmap/envutils_test.go create mode 100644 pkg/envmap/suite_test.go diff --git a/pkg/envmap/doc.go b/pkg/envmap/doc.go new file mode 100644 index 00000000..1cf9b6ee --- /dev/null +++ b/pkg/envmap/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The CloudNativePG Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package envmap is a set of utility functions to manage a process +// environment as a map between the environment variable names and their +// value +package envmap diff --git a/pkg/envmap/envutils.go b/pkg/envmap/envutils.go new file mode 100644 index 00000000..ba511988 --- /dev/null +++ b/pkg/envmap/envutils.go @@ -0,0 +1,91 @@ +/* +Copyright The CloudNativePG Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package envmap + +import ( + "fmt" + "maps" + "os" + "strings" + + "github.com/cloudnative-pg/machinery/pkg/stringset" +) + +// ErrWrongEnvironmentString is raised when the Parse function detects +// a wrong environment string. +// By convention, the strings in environment should have the form +// "name=value". +// +// See: https://www.man7.org/linux/man-pages/man7/environ.7.html +type ErrWrongEnvironmentString struct { + entry string +} + +// Error implements the error interface. +func (e *ErrWrongEnvironmentString) Error() string { + return fmt.Sprintf("wrong environment variable entry: %s", e.entry) +} + +// EnvironmentMap represent a map between environment variable names +// and their value. +type EnvironmentMap map[string]string + +// Parse parses a list of strings in the form "foo=bar" into an environment map. +// If an envalid string is detected, this function will return a +// ErrWrongEnvironmentString error for the invalid entry. +func Parse(env []string) (EnvironmentMap, error) { + result := make(map[string]string, len(env)) + + for _, entry := range env { + prefix, suffix, found := strings.Cut(entry, "=") + if !found { + return nil, &ErrWrongEnvironmentString{ + entry: entry, + } + } + + result[prefix] = suffix + } + + return result, nil +} + +// StringSlice converts an environment map to a list of strings in the form "foo=bar". +// The returned list is sorted in lexicographic key order. +func (e EnvironmentMap) StringSlice() []string { + keys := stringset.FromKeys(e).ToSortedList() + result := make([]string, len(keys)) + for i, keyName := range keys { + result[i] = fmt.Sprintf("%s=%s", keyName, e[keyName]) + } + return result +} + +// ParseEnviron returns the environment of the current process. +func ParseEnviron() (EnvironmentMap, error) { + return Parse(os.Environ()) +} + +// Merge merges two environment maps. +// When a key in e1 is also present in e2, +// the value associated with the key in e2 will be used. +func Merge(e1, e2 EnvironmentMap) EnvironmentMap { + result := make(EnvironmentMap, len(e1)+len(e2)) + maps.Copy(result, e1) + maps.Copy(result, e2) + return result +} diff --git a/pkg/envmap/envutils_test.go b/pkg/envmap/envutils_test.go new file mode 100644 index 00000000..8558538c --- /dev/null +++ b/pkg/envmap/envutils_test.go @@ -0,0 +1,185 @@ +/* +Copyright The CloudNativePG Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package envmap + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Environment map", func() { + DescribeTable( + "Environment map parsing", + func(env []string, expectedResult EnvironmentMap, expectedError error) { + result, err := Parse(env) + if expectedError == nil { + Expect(err).ToNot(HaveOccurred()) + } else { + Expect(err).To(Equal(expectedError)) + } + Expect(result).To(Equal(expectedResult)) + }, + Entry( + "nil slice", + nil, + map[string]string{}, + nil, + ), + Entry( + "basic test", + []string{ + "PATH=/usr/local/bin:/usr/bin", + "TERM=xterm-256color", + }, + map[string]string{ + "PATH": "/usr/local/bin:/usr/bin", + "TERM": "xterm-256color", + }, + nil, + ), + Entry( + "duplicate key", + []string{ + "PATH=/usr/local/bin:/usr/bin", + "PATH=/usr/bin", + }, + map[string]string{ + "PATH": "/usr/bin", + }, + nil, + ), + Entry( + "wrong entry (too many equal signs)", + []string{ + "PATH=/usr/local/bin:/usr/bin", + "TERM=xterm-256color=boh", + }, + map[string]string{ + "PATH": "/usr/local/bin:/usr/bin", + "TERM": "xterm-256color=boh", + }, + nil, + ), + Entry( + "wrong entry (no equal sign)", + []string{ + "PATH=/usr/local/bin:/usr/bin", + "TERM", + }, + nil, + &ErrWrongEnvironmentString{ + entry: "TERM", + }, + ), + ) + + It("parses the current environment", func() { + envMap, err := ParseEnviron() + Expect(err).ToNot(HaveOccurred()) + Expect(envMap).ToNot(BeNil()) + }) + + DescribeTable( + "Environment map merging", + func(e1, e2, result EnvironmentMap) { + Expect(Merge(e1, e2)).To(Equal(result)) + }, + Entry( + "nil arguments", + nil, + nil, + map[string]string{}, + ), + Entry( + "e1 is nil", + nil, + map[string]string{ + "PATH": "/usr/local/bin", + }, + map[string]string{ + "PATH": "/usr/local/bin", + }, + ), + Entry( + "e2 is nil", + map[string]string{ + "PATH": "/usr/local/bin", + }, + nil, + map[string]string{ + "PATH": "/usr/local/bin", + }, + ), + Entry( + "e1 and e2 have no common keys", + map[string]string{ + "PATH": "/usr/local/bin", + }, + map[string]string{ + "TERM": "xterm-256color", + }, + map[string]string{ + "PATH": "/usr/local/bin", + "TERM": "xterm-256color", + }, + ), + Entry( + "e1 and e2 have common keys, e2 will override e1", + map[string]string{ + "PATH": "/usr/local/bin", + "TERM": "xterm-256color", + }, + map[string]string{ + "PATH": "/etc", + }, + map[string]string{ + "PATH": "/etc", + "TERM": "xterm-256color", + }, + ), + ) + + DescribeTable( + "Environment map StringSlice", + func(e EnvironmentMap, expectedResult []string) { + Expect(e.StringSlice()).To(Equal(expectedResult)) + }, + Entry( + "nil environment map", + nil, + []string{}, + ), + Entry( + "empty environment map", + map[string]string{}, + []string{}, + ), + Entry( + "environment map sorted in lexicographical order", + map[string]string{ + "TERM": "xterm-256color", + "PATH": "/usr/local/bin", + "GPG_TTY": "/dev/ttys008", + }, + []string{ + "GPG_TTY=/dev/ttys008", + "PATH=/usr/local/bin", + "TERM=xterm-256color", + }, + ), + ) +}) diff --git a/pkg/envmap/suite_test.go b/pkg/envmap/suite_test.go new file mode 100644 index 00000000..d634e343 --- /dev/null +++ b/pkg/envmap/suite_test.go @@ -0,0 +1,33 @@ +/* +Copyright The CloudNativePG Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package envmap + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +func TestEnvUtils(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Environment variables utils test") +}