Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a7e2329
[#1587] Exclude a common part of CLI parser
fivitti Jan 2, 2025
ac04f65
[#1587] Working general CLI parser
fivitti Jan 3, 2025
ffd2cf4
[#1587] Fix the wrong argument
fivitti Jan 7, 2025
8808118
[#1587] Unify agent and server parsers
fivitti Jan 7, 2025
e301a43
[#1587] Verify the envvars
fivitti Jan 7, 2025
30c4d21
[#1587] Fix linter issues
fivitti Jan 7, 2025
c8ca1c5
[#1587] Verify system environment variables
fivitti Jan 7, 2025
81b857a
[#1587] Add unit tests
fivitti Jan 7, 2025
48add73
[#1587] Extend unit test
fivitti Jan 7, 2025
06ba0b6
[#1587] Fix unit tests
fivitti Jan 7, 2025
cfb6ec4
[#1587] Simplify utility
fivitti Jan 7, 2025
7a29eb8
[#1587] Fix linter issue
fivitti Jan 8, 2025
522be1b
[#1587] Remove redundant flags
fivitti Jan 8, 2025
c6657d4
[#1587] Add unit tests
fivitti Jan 8, 2025
bf98f00
[#1587] Add a Changelog entry
fivitti Jan 8, 2025
19b7c94
[#1587] Unify the CLI handling in the Stork tool
fivitti Jan 8, 2025
66db02f
[#1587] Move package
fivitti Jan 8, 2025
425910d
[#1587] Exclude app to a separate file
fivitti Jan 8, 2025
7b201bd
[#1587] Unexport structs
fivitti Jan 8, 2025
6569020
[#1587] Unify code-gen CLI
fivitti Jan 8, 2025
27c6b7c
[#1587] Remove unnecessary dependencies
fivitti Jan 8, 2025
185123d
[#1587] Rename structs
fivitti Jan 9, 2025
f1d16f2
[#1587] Add unit tests
fivitti Jan 9, 2025
60f2fe4
[#1587] Rephrase a sentence
fivitti Jan 9, 2025
1acfd8d
[#1587] Support hooks only for agent and server
fivitti Jan 9, 2025
9285a3c
[#1587] Add unit test
fivitti Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[#1587] Add unit tests
  • Loading branch information
fivitti authored and tomaszmrugalski committed Jun 2, 2025
commit c6657d4627f0784449642bb0b412ef1935477d6e
55 changes: 52 additions & 3 deletions backend/appcfg/stork/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ type CLIParser struct {
}

// Constructs CLI parser.
// Accepts the main parser. It should be configured with the application
// settings.
// The application name is used to construct the namespaces for the CLI flags
// and environment variables. It should be either 'server' or 'agent'.
// The callback is called when the environment file is loaded. Its purpose is
// to allow reconfiguring the logging using the new environment variables as
// soon as they are available.
func NewCLIParser(parser *flags.Parser, app string, onLoadEnvironmentFileCallback func()) *CLIParser {
if app != "server" && app != "agent" {
// Programming error.
Expand Down Expand Up @@ -312,8 +319,43 @@ func (p *CLIParser) verifySystemEnvironmentVariables() {
}
}

// The prefix that is used for the Stork-specific environment variables.
applicationPrefix := fmt.Sprintf("STORK_%s_", strings.ToUpper(p.application))
// Contains the prefixes of the Stork-specific environment variables.
// Stork environment variables starts with the 'STORK' part and then
// the context-specific part e.g.:
//
// - STORK_SERVER (server-specific environment variable)
// - STORK_AGENT (agent-specific environment variable)
// - STORK_DATABASE (database-specific environment variable)
//
// This function analyzes the system-wide environment variables. We cannot
// assume that there are only environment variables related to one of the
// Stork components. It should be allowed to have the agent, server, and
// tool environment variables set in the same shell.
//
// The below code block allows us to ignore the environment variables from
// other Stork components. There is an assumption that all components have
// exactly the same environment variables for a given prefix/namespace.
// For example, if the application utilizes the environment variables
// prefixed with 'STORK_DATABASE_' then its settings specify exactly the
// same environment variables as the other components.
// I hope it is fair enough.
var prefixes []string

for environmentVariable := range knownEnvironmentVariables {
parts := strings.SplitN(environmentVariable, "_", 3)
if len(parts) < 3 {
// The environment variable doesn't have the context-specific part.
// The naming convention is violated.
continue
}
if parts[0] != "STORK" {
// The environment variable doesn't start with the 'STORK' part.
// The naming convention is violated.
continue
}

prefixes = append(prefixes, fmt.Sprintf("%s_%s_", parts[0], parts[1]))
}

// Iterate over all system-wide environment variables.
for _, env := range os.Environ() {
Expand All @@ -323,7 +365,14 @@ func (p *CLIParser) verifySystemEnvironmentVariables() {
continue
}

if !strings.HasPrefix(key, applicationPrefix) {
var isApplicationEnvironmentVariable bool
for _, prefix := range prefixes {
if strings.HasPrefix(key, prefix) {
isApplicationEnvironmentVariable = true
break
}
}
if !isApplicationEnvironmentVariable {
// Not a Stork-specific environment variable.
continue
}
Expand Down
110 changes: 110 additions & 0 deletions backend/appcfg/stork/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,14 +491,19 @@ func TestVerifySystemEnvironmentVariables(t *testing.T) {

type settings struct {
TLSCert string `long:"tls-cert" env:"STORK_SERVER_TLS_CERT" description:"The path to the TLS certificate"`
DBHost string `long:"db-host" env:"STORK_DATABASE_HOST" description:"The host name, IP address or socket where database is available"`
}
data := &settings{}

parser := NewCLIParser(flags.NewParser(data, flags.Default), "server", func() {})

os.Setenv("STORK_SERVER_UNKNOWN", "unknown")
os.Setenv("STORK_SERVER_TLS_CERT", "tlsCert")
os.Setenv("STORK_DATABASE_HOST", "databaseHost")
os.Setenv("STORK_DATABASE_UNKNOWN", "databaseUnknown")
os.Setenv("FOOBAR", "foobar")
// Broken naming convention.
os.Setenv("STORK_UNKNOWN", "unknown")

// Act
stdout, _, captureErr := testutil.CaptureOutput(func() {
Expand All @@ -510,6 +515,111 @@ func TestVerifySystemEnvironmentVariables(t *testing.T) {

expectedLog := `Unknown environment variable: 'STORK_SERVER_UNKNOWN' set in a shell`
require.Contains(t, string(stdout), expectedLog)
require.Contains(t, string(stdout), "Unknown environment variable: 'STORK_DATABASE_UNKNOWN' set in a shell")
require.NotContains(t, string(stdout), "TLS_CERT")
require.NotContains(t, string(stdout), "FOOBAR")
require.NotContains(t, string(stdout), "DATABASE_HOST")
require.NotContains(t, string(stdout), "STORK_UNKNOWN")
}

// Test that the environment variables from another Stork application set in
// the shell don't raise a warning.
func TestVerifySystemEnvironmentVariablesFromAnotherApplication(t *testing.T) {
// Arrange
restore := testutil.CreateEnvironmentRestorePoint()
defer restore()

type settings struct {
TLSCert string `long:"tls-cert" env:"STORK_SERVER_TLS_CERT" description:"The path to the TLS certificate"`
}
data := &settings{}

parser := NewCLIParser(flags.NewParser(data, flags.Default), "server", func() {})

// Act
os.Setenv("STORK_AGENT_TLS_CERT", "tlsCert")

stdout, _, captureErr := testutil.CaptureOutput(func() {
parser.verifySystemEnvironmentVariables()
})

// Assert
require.NoError(t, captureErr)
require.NotContains(t, string(stdout), "TLS_CERT")
}

// Test that the callback is called when the environment file is loaded.
func TestOnEnvironmentFileLoadedCallbackIsCalled(t *testing.T) {
// Arrange
restore := testutil.CreateEnvironmentRestorePoint()
defer restore()

sandbox := testutil.NewSandbox()
defer sandbox.Close()

envPath, _ := sandbox.Write("file.env", `
STORK_SERVER_TLS_CERT=tlsCert
`)

isCalled := false
callback := func() {
isCalled = true
}

parser := NewCLIParser(flags.NewParser(&struct{}{}, flags.Default), "server", callback)

// Act
err := parser.loadEnvironmentFile(&environmentFileSettings{
EnvFile: envPath,
UseEnvFile: true,
})

// Assert
require.NoError(t, err)
require.True(t, isCalled)
}

// Test the callback is called when the environment file is loaded exactly once.
func TestOnEnvironmentFileLoadedCallbackIsCalledOnce(t *testing.T) {
// Arrange
restorePoint := testutil.CreateEnvironmentRestorePoint()
defer restorePoint()
sandbox := testutil.NewSandbox()
defer sandbox.Close()

envPath, _ := sandbox.Write("file.env", `
STORK_DATABASE_HOST=foo
STORK_SERVER_HOOK_DIRECTORY=bar
STORK_REST_HOST=baz
`)

defer testutil.CreateOsArgsRestorePoint()()
os.Args = []string{
"program-name",
"--use-env-file",
"--env-file", envPath,
}

type settings struct {
DBHost string `long:"db-host" description:"The host name, IP address or socket where database is available" env:"STORK_DATABASE_HOST" default:""`
RESTHost string `long:"rest-host" description:"The IP to listen on" default:"" env:"STORK_REST_HOST"`
}

data := &settings{}

flagParser := flags.NewParser(data, flags.Default)

parser := NewCLIParser(flagParser, "server", func() {})

// Act
hookDirSettings, hookFlags, isHelp, err := parser.Parse()

// Assert
require.NoError(t, err)
require.False(t, isHelp)
require.Empty(t, hookFlags)

require.Equal(t, "foo", data.DBHost)
require.Equal(t, "bar", hookDirSettings.HookDirectory)
require.Equal(t, "baz", data.RESTHost)
}