blob: eb29eb271ff41e05c87707a9d956ef840b1ecb0d [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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 app provides application primitives.
package app
import (
"context"
"errors"
"fmt"
"io"
"os"
"sort"
"strconv"
)
import (
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/interrupt"
)
// EnvContainer provides environment variables.
type EnvContainer interface {
// Env gets the environment variable raw string value for the key.
//
// Returns empty string if the key is not set or the value is empty.
Env(key string) string
// ForEachEnv iterates over all non-empty environment variables and calls the function.
//
// The value will never be empty.
ForEachEnv(func(string, string))
}
// NewEnvContainer returns a new EnvContainer.
//
// Empty values are effectively ignored.
func NewEnvContainer(m map[string]string) EnvContainer {
return newEnvContainer(m)
}
// NewEnvContainerForOS returns a new EnvContainer for the operating system.
func NewEnvContainerForOS() (EnvContainer, error) {
return newEnvContainerForEnviron(os.Environ())
}
// NewEnvContainerWithOverrides returns a new EnvContainer with the values of the input
// EnvContainer, overridden by the values in overrides.
//
// Empty values are effectively ignored. To unset a key, set the value to "" in overrides.
func NewEnvContainerWithOverrides(envContainer EnvContainer, overrides map[string]string) EnvContainer {
m := EnvironMap(envContainer)
for key, value := range overrides {
m[key] = value
}
return newEnvContainer(m)
}
// StdinContainer provides stdin.
type StdinContainer interface {
// Stdin provides stdin.
//
// If no value was passed when Stdio was created, this will return io.EOF on any call.
Stdin() io.Reader
}
// NewStdinContainer returns a new StdinContainer.
func NewStdinContainer(reader io.Reader) StdinContainer {
return newStdinContainer(reader)
}
// NewStdinContainerForOS returns a new StdinContainer for the operating system.
func NewStdinContainerForOS() StdinContainer {
return newStdinContainer(os.Stdin)
}
// StdoutContainer provides stdout.
type StdoutContainer interface {
// Stdout provides stdout.
//
// If no value was passed when Stdio was created, this will return io.EOF on any call.
Stdout() io.Writer
}
// NewStdoutContainer returns a new StdoutContainer.
func NewStdoutContainer(writer io.Writer) StdoutContainer {
return newStdoutContainer(writer)
}
// NewStdoutContainerForOS returns a new StdoutContainer for the operatoutg system.
func NewStdoutContainerForOS() StdoutContainer {
return newStdoutContainer(os.Stdout)
}
// StderrContainer provides stderr.
type StderrContainer interface {
// Stderr provides stderr.
//
// If no value was passed when Stdio was created, this will return io.EOF on any call.
Stderr() io.Writer
}
// NewStderrContainer returns a new StderrContainer.
func NewStderrContainer(writer io.Writer) StderrContainer {
return newStderrContainer(writer)
}
// NewStderrContainerForOS returns a new StderrContainer for the operaterrg system.
func NewStderrContainerForOS() StderrContainer {
return newStderrContainer(os.Stderr)
}
// ArgContainer provides the arguments.
type ArgContainer interface {
// NumArgs gets the number of arguments.
NumArgs() int
// Arg gets the ith argument.
//
// Panics if i < 0 || i >= Len().
Arg(i int) string
}
// NewArgContainer returns a new ArgContainer.
func NewArgContainer(args ...string) ArgContainer {
return newArgContainer(args)
}
// NewArgContainerForOS returns a new ArgContainer for the operating system.
func NewArgContainerForOS() ArgContainer {
return newArgContainer(os.Args)
}
// Container contains environment variables, args, and stdio.
type Container interface {
EnvContainer
StdinContainer
StdoutContainer
StderrContainer
ArgContainer
}
// NewContainer returns a new Container.
func NewContainer(
env map[string]string,
stdin io.Reader,
stdout io.Writer,
stderr io.Writer,
args ...string,
) Container {
return newContainer(
NewEnvContainer(env),
NewStdinContainer(stdin),
NewStdoutContainer(stdout),
NewStderrContainer(stderr),
NewArgContainer(args...),
)
}
// NewContainerForOS returns a new Container for the operating system.
func NewContainerForOS() (Container, error) {
envContainer, err := NewEnvContainerForOS()
if err != nil {
return nil, err
}
return newContainer(
envContainer,
NewStdinContainerForOS(),
NewStdoutContainerForOS(),
NewStderrContainerForOS(),
NewArgContainerForOS(),
), nil
}
// NewContainerForArgs returns a new Container with the replacement args.
func NewContainerForArgs(container Container, newArgs ...string) Container {
return newContainer(
container,
container,
container,
container,
NewArgContainer(newArgs...),
)
}
// StdioContainer is a stdio container.
type StdioContainer interface {
StdinContainer
StdoutContainer
StderrContainer
}
// EnvStdinContainer is an environment and stdin container.
type EnvStdinContainer interface {
EnvContainer
StdinContainer
}
// EnvStdoutContainer is an environment and stdout container.
type EnvStdoutContainer interface {
EnvContainer
StdoutContainer
}
// EnvStderrContainer is an environment and stderr container.
type EnvStderrContainer interface {
EnvContainer
StderrContainer
}
// EnvStdioContainer is an environment and stdio container.
type EnvStdioContainer interface {
EnvContainer
StdioContainer
}
// Environ returns all environment variables in the form "KEY=VALUE".
//
// Equivalent to os.Environ.
//
// Sorted.
func Environ(envContainer EnvContainer) []string {
var environ []string
envContainer.ForEachEnv(func(key string, value string) {
environ = append(environ, key+"="+value)
})
sort.Strings(environ)
return environ
}
// EnvironMap returns all environment variables in a map.
//
// No key will have an empty value.
func EnvironMap(envContainer EnvContainer) map[string]string {
m := make(map[string]string)
envContainer.ForEachEnv(func(key string, value string) {
// This should be done anyways per the EnvContainer documentation but just to make sure
if value != "" {
m[key] = value
}
})
return m
}
// Args returns all arguments.
//
// Equivalent to os.Args.
func Args(argList ArgContainer) []string {
numArgs := argList.NumArgs()
args := make([]string, numArgs)
for i := 0; i < numArgs; i++ {
args[i] = argList.Arg(i)
}
return args
}
// EnvBool EnvBoolValue gets and parses the environment variable bool value for the key.
//
// Returns error on parsing error.
func EnvBool(container EnvContainer, key string, defaultValue bool) (bool, error) {
value := container.Env(key)
if value == "" {
return defaultValue, nil
}
return strconv.ParseBool(value)
}
// IsDevStdin returns true if the path is the equivalent of /dev/stdin.
func IsDevStdin(path string) bool {
return path != "" && path == DevStdinFilePath
}
// IsDevStdout returns true if the path is the equivalent of /dev/stdout.
func IsDevStdout(path string) bool {
return path != "" && path == DevStdoutFilePath
}
// IsDevStderr returns true if the path is the equivalent of /dev/stderr.
func IsDevStderr(path string) bool {
return path != "" && path == DevStderrFilePath
}
// IsDevNull returns true if the path is the equivalent of /dev/null.
func IsDevNull(path string) bool {
return path != "" && path == DevNullFilePath
}
// IsDevPath returns true if the path is the equivalent of /dev/stdin, /dev/stdout,
// /dev/stderr, or /dev/null.
func IsDevPath(path string) bool {
return IsDevStdin(path) || IsDevStdout(path) || IsDevStderr(path) || IsDevNull(path)
}
// Main runs the application using the OS Container and calling os.Exit on the return value of Run.
func Main(ctx context.Context, f func(context.Context, Container) error) {
container, err := NewContainerForOS()
if err != nil {
printError(container, err)
os.Exit(GetExitCode(err))
}
os.Exit(GetExitCode(Run(ctx, container, f)))
}
// Run runs the application using the container.
//
// The run will be stopped on interrupt signal.
// The exit code can be determined using GetExitCode.
func Run(ctx context.Context, container Container, f func(context.Context, Container) error) error {
ctx, cancel := interrupt.WithCancel(ctx)
defer cancel()
if err := f(ctx, container); err != nil {
printError(container, err)
return err
}
return nil
}
// NewError returns a new Error that contains an exit code.
//
// The exit code cannot be 0.
func NewError(exitCode int, message string) error {
return newAppError(exitCode, message)
}
// NewErrorf returns a new error that contains an exit code.
//
// The exit code cannot be 0.
func NewErrorf(exitCode int, format string, args ...interface{}) error {
return newAppError(exitCode, fmt.Sprintf(format, args...))
}
// GetExitCode gets the exit code.
//
// If err == nil, this returns 0.
// If err was created by this package, this returns the exit code from the error.
// Otherwise, this returns 1.
func GetExitCode(err error) int {
if err == nil {
return 0
}
appErr := &appError{}
if errors.As(err, &appErr) {
return appErr.exitCode
}
return 1
}