mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-07-09 21:17:09 +08:00
update github.com/compose-spec/compose-go to v1.3.0
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
107
vendor/github.com/compose-spec/compose-go/dotenv/godotenv.go
generated
vendored
107
vendor/github.com/compose-spec/compose-go/dotenv/godotenv.go
generated
vendored
@ -17,13 +17,14 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/template"
|
||||
)
|
||||
|
||||
const doubleQuoteSpecialChars = "\\\n\r\"!$`"
|
||||
@ -42,7 +43,7 @@ func Parse(r io.Reader) (map[string]string, error) {
|
||||
|
||||
// ParseWithLookup reads an env file from io.Reader, returning a map of keys and values.
|
||||
func ParseWithLookup(r io.Reader, lookupFn LookupFn) (map[string]string, error) {
|
||||
data, err := ioutil.ReadAll(r)
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -61,7 +62,7 @@ func ParseWithLookup(r io.Reader, lookupFn LookupFn) (map[string]string, error)
|
||||
// godotenv.Load("fileone", "filetwo")
|
||||
//
|
||||
// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
|
||||
func Load(filenames ...string) (err error) {
|
||||
func Load(filenames ...string) error {
|
||||
return load(false, filenames...)
|
||||
}
|
||||
|
||||
@ -76,52 +77,55 @@ func Load(filenames ...string) (err error) {
|
||||
// godotenv.Overload("fileone", "filetwo")
|
||||
//
|
||||
// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars.
|
||||
func Overload(filenames ...string) (err error) {
|
||||
func Overload(filenames ...string) error {
|
||||
return load(true, filenames...)
|
||||
}
|
||||
|
||||
func load(overload bool, filenames ...string) (err error) {
|
||||
func load(overload bool, filenames ...string) error {
|
||||
filenames = filenamesOrDefault(filenames)
|
||||
|
||||
for _, filename := range filenames {
|
||||
err = loadFile(filename, overload)
|
||||
err := loadFile(filename, overload)
|
||||
if err != nil {
|
||||
return // return early on a spazout
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
var startsWithDigitRegex = regexp.MustCompile(`^\s*\d.*`) // Keys starting with numbers are ignored
|
||||
|
||||
// ReadWithLookup gets all env vars from the files and/or lookup function and return values as
|
||||
// a map rather than automatically writing values into env
|
||||
func ReadWithLookup(lookupFn LookupFn, filenames ...string) (envMap map[string]string, err error) {
|
||||
func ReadWithLookup(lookupFn LookupFn, filenames ...string) (map[string]string, error) {
|
||||
filenames = filenamesOrDefault(filenames)
|
||||
envMap = make(map[string]string)
|
||||
envMap := make(map[string]string)
|
||||
|
||||
for _, filename := range filenames {
|
||||
individualEnvMap, individualErr := readFile(filename, lookupFn)
|
||||
|
||||
if individualErr != nil {
|
||||
err = individualErr
|
||||
return // return early on a spazout
|
||||
return envMap, individualErr
|
||||
}
|
||||
|
||||
for key, value := range individualEnvMap {
|
||||
if startsWithDigitRegex.MatchString(key) {
|
||||
continue
|
||||
}
|
||||
envMap[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return envMap, nil
|
||||
}
|
||||
|
||||
// Read all env (with same file loading semantics as Load) but return values as
|
||||
// a map rather than automatically writing values into env
|
||||
func Read(filenames ...string) (envMap map[string]string, err error) {
|
||||
func Read(filenames ...string) (map[string]string, error) {
|
||||
return ReadWithLookup(nil, filenames...)
|
||||
}
|
||||
|
||||
// Unmarshal reads an env file from a string, returning a map of keys and values.
|
||||
func Unmarshal(str string) (envMap map[string]string, err error) {
|
||||
func Unmarshal(str string) (map[string]string, error) {
|
||||
return UnmarshalBytes([]byte(str))
|
||||
}
|
||||
|
||||
@ -182,7 +186,7 @@ func Marshal(envMap map[string]string) (string, error) {
|
||||
if d, err := strconv.Atoi(v); err == nil {
|
||||
lines = append(lines, fmt.Sprintf(`%s=%d`, k, d))
|
||||
} else {
|
||||
lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v)))
|
||||
lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v))) // nolint // Cannot use %q here
|
||||
}
|
||||
}
|
||||
sort.Strings(lines)
|
||||
@ -218,10 +222,10 @@ func loadFile(filename string, overload bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readFile(filename string, lookupFn LookupFn) (envMap map[string]string, err error) {
|
||||
func readFile(filename string, lookupFn LookupFn) (map[string]string, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
@ -230,28 +234,25 @@ func readFile(filename string, lookupFn LookupFn) (envMap map[string]string, err
|
||||
|
||||
var exportRegex = regexp.MustCompile(`^\s*(?:export\s+)?(.*?)\s*$`)
|
||||
|
||||
func parseLine(line string, envMap map[string]string) (key string, value string, err error) {
|
||||
func parseLine(line string, envMap map[string]string) (string, string, error) {
|
||||
return parseLineWithLookup(line, envMap, nil)
|
||||
}
|
||||
func parseLineWithLookup(line string, envMap map[string]string, lookupFn LookupFn) (key string, value string, err error) {
|
||||
if len(line) == 0 {
|
||||
err = errors.New("zero length string")
|
||||
return
|
||||
func parseLineWithLookup(line string, envMap map[string]string, lookupFn LookupFn) (string, string, error) {
|
||||
if line == "" {
|
||||
return "", "", errors.New("zero length string")
|
||||
}
|
||||
|
||||
// ditch the comments (but keep quoted hashes)
|
||||
if strings.Contains(line, "#") {
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "#") || strings.Contains(line, " #") {
|
||||
segmentsBetweenHashes := strings.Split(line, "#")
|
||||
quotesAreOpen := false
|
||||
var segmentsToKeep []string
|
||||
for _, segment := range segmentsBetweenHashes {
|
||||
if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 {
|
||||
if quotesAreOpen {
|
||||
quotesAreOpen = false
|
||||
segmentsToKeep = append(segmentsToKeep, segment)
|
||||
} else {
|
||||
quotesAreOpen = true
|
||||
}
|
||||
quotesAreOpen = !quotesAreOpen
|
||||
}
|
||||
|
||||
if len(segmentsToKeep) == 0 || quotesAreOpen {
|
||||
@ -271,14 +272,14 @@ func parseLineWithLookup(line string, envMap map[string]string, lookupFn LookupF
|
||||
}
|
||||
|
||||
if len(splitString) != 2 {
|
||||
err = errors.New("can't separate key from value")
|
||||
return
|
||||
return "", "", errors.New("can't separate key from value")
|
||||
}
|
||||
key = exportRegex.ReplaceAllString(splitString[0], "$1")
|
||||
key := exportRegex.ReplaceAllString(splitString[0], "$1")
|
||||
|
||||
// Parse the value
|
||||
value = parseValue(splitString[1], envMap, lookupFn)
|
||||
return
|
||||
value := parseValue(splitString[1], envMap, lookupFn)
|
||||
|
||||
return key, value, nil
|
||||
}
|
||||
|
||||
var (
|
||||
@ -322,42 +323,24 @@ func parseValue(value string, envMap map[string]string, lookupFn LookupFn) strin
|
||||
}
|
||||
|
||||
if singleQuotes == nil {
|
||||
value = expandVariables(value, envMap, lookupFn)
|
||||
value, _ = expandVariables(value, envMap, lookupFn)
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
var expandVarRegex = regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`)
|
||||
|
||||
func expandVariables(v string, envMap map[string]string, lookupFn LookupFn) string {
|
||||
return expandVarRegex.ReplaceAllStringFunc(v, func(s string) string {
|
||||
submatch := expandVarRegex.FindStringSubmatch(s)
|
||||
|
||||
if submatch == nil {
|
||||
return s
|
||||
func expandVariables(value string, envMap map[string]string, lookupFn LookupFn) (string, error) {
|
||||
retVal, err := template.Substitute(value, func(k string) (string, bool) {
|
||||
if v, ok := envMap[k]; ok {
|
||||
return v, ok
|
||||
}
|
||||
if submatch[1] == "\\" || submatch[2] == "(" {
|
||||
return submatch[0][1:]
|
||||
} else if submatch[4] != "" {
|
||||
// first check if we have defined this already earlier
|
||||
if envMap[submatch[4]] != "" {
|
||||
return envMap[submatch[4]]
|
||||
}
|
||||
if lookupFn == nil {
|
||||
return ""
|
||||
}
|
||||
// if we have not defined it, check the lookup function provided
|
||||
// by the user
|
||||
s2, ok := lookupFn(submatch[4])
|
||||
if ok {
|
||||
return s2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
return lookupFn(k)
|
||||
})
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
return retVal, nil
|
||||
}
|
||||
|
||||
func doubleQuoteEscape(line string) string {
|
||||
@ -369,7 +352,7 @@ func doubleQuoteEscape(line string) string {
|
||||
if c == '\r' {
|
||||
toReplace = `\r`
|
||||
}
|
||||
line = strings.Replace(line, string(c), toReplace, -1)
|
||||
line = strings.ReplaceAll(line, string(c), toReplace)
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
41
vendor/github.com/compose-spec/compose-go/dotenv/parser.go
generated
vendored
41
vendor/github.com/compose-spec/compose-go/dotenv/parser.go
generated
vendored
@ -37,7 +37,6 @@ func parseBytes(src []byte, out map[string]string, lookupFn LookupFn) error {
|
||||
}
|
||||
|
||||
if inherited {
|
||||
|
||||
value, ok := lookupFn(key)
|
||||
if ok {
|
||||
out[key] = value
|
||||
@ -50,9 +49,6 @@ func parseBytes(src []byte, out map[string]string, lookupFn LookupFn) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lookUpValue, ok := lookupFn(key); ok {
|
||||
value = lookUpValue
|
||||
}
|
||||
|
||||
out[key] = value
|
||||
cutset = left
|
||||
@ -85,7 +81,9 @@ func getStatementStart(src []byte) []byte {
|
||||
}
|
||||
|
||||
// locateKeyName locates and parses key name and returns rest of slice
|
||||
func locateKeyName(src []byte) (key string, cutset []byte, inherited bool, err error) {
|
||||
func locateKeyName(src []byte) (string, []byte, bool, error) {
|
||||
var key string
|
||||
var inherited bool
|
||||
// trim "export" and space at beginning
|
||||
src = bytes.TrimLeftFunc(bytes.TrimPrefix(src, []byte(exportPrefix)), isSpace)
|
||||
|
||||
@ -105,9 +103,9 @@ loop:
|
||||
offset = i + 1
|
||||
inherited = char == '\n'
|
||||
break loop
|
||||
case '_':
|
||||
case '_', '.':
|
||||
default:
|
||||
// variable name should match [A-Za-z0-9_]
|
||||
// variable name should match [A-Za-z0-9_.]
|
||||
if unicode.IsLetter(rchar) || unicode.IsNumber(rchar) {
|
||||
continue
|
||||
}
|
||||
@ -124,12 +122,12 @@ loop:
|
||||
|
||||
// trim whitespace
|
||||
key = strings.TrimRightFunc(key, unicode.IsSpace)
|
||||
cutset = bytes.TrimLeftFunc(src[offset:], isSpace)
|
||||
cutset := bytes.TrimLeftFunc(src[offset:], isSpace)
|
||||
return key, cutset, inherited, nil
|
||||
}
|
||||
|
||||
// extractVarValue extracts variable value and returns rest of slice
|
||||
func extractVarValue(src []byte, envMap map[string]string, lookupFn LookupFn) (value string, rest []byte, err error) {
|
||||
func extractVarValue(src []byte, envMap map[string]string, lookupFn LookupFn) (string, []byte, error) {
|
||||
quote, isQuoted := hasQuotePrefix(src)
|
||||
if !isQuoted {
|
||||
// unquoted value - read until new line
|
||||
@ -138,13 +136,17 @@ func extractVarValue(src []byte, envMap map[string]string, lookupFn LookupFn) (v
|
||||
if end < 0 {
|
||||
value := strings.Split(string(src), "#")[0] // Remove inline comments on unquoted lines
|
||||
value = strings.TrimRightFunc(value, unicode.IsSpace)
|
||||
return expandVariables(value, envMap, lookupFn), nil, nil
|
||||
|
||||
retVal, err := expandVariables(value, envMap, lookupFn)
|
||||
return retVal, nil, err
|
||||
}
|
||||
|
||||
value := strings.Split(string(src[0:end]), "#")[0]
|
||||
value = strings.TrimRightFunc(value, unicode.IsSpace)
|
||||
rest = src[end:]
|
||||
return expandVariables(value, envMap, lookupFn), rest, nil
|
||||
|
||||
retVal, err := expandVariables(value, envMap, lookupFn)
|
||||
return retVal, rest, err
|
||||
}
|
||||
|
||||
// lookup quoted string terminator
|
||||
@ -160,11 +162,16 @@ func extractVarValue(src []byte, envMap map[string]string, lookupFn LookupFn) (v
|
||||
|
||||
// trim quotes
|
||||
trimFunc := isCharFunc(rune(quote))
|
||||
value = string(bytes.TrimLeftFunc(bytes.TrimRightFunc(src[0:i], trimFunc), trimFunc))
|
||||
value := string(bytes.TrimLeftFunc(bytes.TrimRightFunc(src[0:i], trimFunc), trimFunc))
|
||||
if quote == prefixDoubleQuote {
|
||||
// unescape newlines for double quote (this is compat feature)
|
||||
// and expand environment variables
|
||||
value = expandVariables(expandEscapes(value), envMap, lookupFn)
|
||||
|
||||
retVal, err := expandVariables(expandEscapes(value), envMap, lookupFn)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
value = retVal
|
||||
}
|
||||
|
||||
return value, src[i+1:], nil
|
||||
@ -187,6 +194,8 @@ func expandEscapes(str string) string {
|
||||
return "\n"
|
||||
case "r":
|
||||
return "\r"
|
||||
case "$":
|
||||
return "$$"
|
||||
default:
|
||||
return match
|
||||
}
|
||||
@ -201,14 +210,14 @@ func indexOfNonSpaceChar(src []byte) int {
|
||||
}
|
||||
|
||||
// hasQuotePrefix reports whether charset starts with single or double quote and returns quote character
|
||||
func hasQuotePrefix(src []byte) (quote byte, isQuoted bool) {
|
||||
func hasQuotePrefix(src []byte) (byte, bool) {
|
||||
if len(src) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
switch prefix := src[0]; prefix {
|
||||
switch quote := src[0]; quote {
|
||||
case prefixDoubleQuote, prefixSingleQuote:
|
||||
return prefix, true
|
||||
return quote, true // isQuoted
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
|
2
vendor/github.com/compose-spec/compose-go/interpolation/interpolation.go
generated
vendored
2
vendor/github.com/compose-spec/compose-go/interpolation/interpolation.go
generated
vendored
@ -115,7 +115,7 @@ func newPathError(path Path, err error) error {
|
||||
return nil
|
||||
case *template.InvalidTemplateError:
|
||||
return errors.Errorf(
|
||||
"invalid interpolation format for %s: %#v. You may need to escape any $ with another $",
|
||||
"invalid interpolation format for %s.\nYou may need to escape any $ with another $.\n%s",
|
||||
path, err.Template)
|
||||
default:
|
||||
return errors.Wrapf(err, "error while interpolating %s", path)
|
||||
|
2
vendor/github.com/compose-spec/compose-go/loader/example1.env
generated
vendored
2
vendor/github.com/compose-spec/compose-go/loader/example1.env
generated
vendored
@ -1,5 +1,7 @@
|
||||
# passed through
|
||||
FOO=foo_from_env_file
|
||||
ENV.WITH.DOT=ok
|
||||
ENV_WITH_UNDERSCORE=ok
|
||||
|
||||
# overridden in example2.env
|
||||
BAR=bar_from_env_file
|
||||
|
1
vendor/github.com/compose-spec/compose-go/loader/full-example.yml
generated
vendored
1
vendor/github.com/compose-spec/compose-go/loader/full-example.yml
generated
vendored
@ -25,6 +25,7 @@ services:
|
||||
tags:
|
||||
- foo:v1.0.0
|
||||
- docker.io/username/foo:my-other-tag
|
||||
- ${COMPOSE_PROJECT_NAME}:1.0.0
|
||||
|
||||
|
||||
cap_add:
|
||||
|
79
vendor/github.com/compose-spec/compose-go/loader/loader.go
generated
vendored
79
vendor/github.com/compose-spec/compose-go/loader/loader.go
generated
vendored
@ -17,10 +17,11 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
paths "path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@ -42,6 +43,11 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultSeparator = "-"
|
||||
CompatibilitySeparator = "_"
|
||||
)
|
||||
|
||||
// Options supported by Load
|
||||
type Options struct {
|
||||
// Skip schema validation
|
||||
@ -66,6 +72,8 @@ type Options struct {
|
||||
projectName string
|
||||
// Indicates when the projectName was imperatively set or guessed from path
|
||||
projectNameImperativelySet bool
|
||||
// Set separator used for naming resources
|
||||
Separator string
|
||||
}
|
||||
|
||||
func (o *Options) SetProjectName(name string, imperativelySet bool) {
|
||||
@ -154,12 +162,15 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
|
||||
LookupValue: configDetails.LookupEnv,
|
||||
TypeCastMapping: interpolateTypeCastMapping,
|
||||
},
|
||||
Separator: DefaultSeparator,
|
||||
}
|
||||
|
||||
for _, op := range options {
|
||||
op(opts)
|
||||
}
|
||||
|
||||
projectName := projectName(configDetails, opts)
|
||||
|
||||
var configs []*types.Config
|
||||
for i, file := range configDetails.ConfigFiles {
|
||||
configDict := file.Config
|
||||
@ -207,15 +218,6 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
|
||||
s.EnvFile = newEnvFiles
|
||||
}
|
||||
|
||||
projectName, projectNameImperativelySet := opts.GetProjectName()
|
||||
model.Name = NormalizeProjectName(model.Name)
|
||||
if !projectNameImperativelySet && model.Name != "" {
|
||||
projectName = model.Name
|
||||
}
|
||||
|
||||
if projectName != "" {
|
||||
configDetails.Environment[consts.ComposeProjectName] = projectName
|
||||
}
|
||||
project := &types.Project{
|
||||
Name: projectName,
|
||||
WorkingDir: configDetails.WorkingDir,
|
||||
@ -229,7 +231,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
|
||||
}
|
||||
|
||||
if !opts.SkipNormalization {
|
||||
err = normalize(project, opts.ResolvePaths)
|
||||
err = normalize(project, opts.ResolvePaths, opts.Separator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -245,6 +247,30 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func projectName(details types.ConfigDetails, opts *Options) string {
|
||||
projectName, projectNameImperativelySet := opts.GetProjectName()
|
||||
var pjNameFromConfigFile string
|
||||
|
||||
for _, configFile := range details.ConfigFiles {
|
||||
yml, err := ParseYAML(configFile.Content)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if val, ok := yml["name"]; ok && val != "" {
|
||||
pjNameFromConfigFile = yml["name"].(string)
|
||||
}
|
||||
}
|
||||
pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile)
|
||||
if !projectNameImperativelySet && pjNameFromConfigFile != "" {
|
||||
projectName = pjNameFromConfigFile
|
||||
}
|
||||
|
||||
if _, ok := details.Environment[consts.ComposeProjectName]; !ok && projectName != "" {
|
||||
details.Environment[consts.ComposeProjectName] = projectName
|
||||
}
|
||||
return projectName
|
||||
}
|
||||
|
||||
func NormalizeProjectName(s string) string {
|
||||
r := regexp.MustCompile("[a-z0-9_-]")
|
||||
s = strings.ToLower(s)
|
||||
@ -507,12 +533,12 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
|
||||
// Resolve the path to the imported file, and load it.
|
||||
baseFilePath := absPath(workingDir, *file)
|
||||
|
||||
bytes, err := ioutil.ReadFile(baseFilePath)
|
||||
b, err := os.ReadFile(baseFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseFile, err := parseConfig(bytes, opts)
|
||||
baseFile, err := parseConfig(b, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -606,14 +632,25 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l
|
||||
environment := types.MappingWithEquals{}
|
||||
|
||||
if len(serviceConfig.EnvFile) > 0 {
|
||||
if serviceConfig.Environment == nil {
|
||||
serviceConfig.Environment = types.MappingWithEquals{}
|
||||
}
|
||||
for _, envFile := range serviceConfig.EnvFile {
|
||||
filePath := absPath(workingDir, envFile)
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
fileVars, err := dotenv.ParseWithLookup(file, dotenv.LookupFn(lookupEnv))
|
||||
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Do not defer to avoid it inside a loop
|
||||
file.Close() //nolint:errcheck
|
||||
|
||||
fileVars, err := dotenv.ParseWithLookup(bytes.NewBuffer(b), dotenv.LookupFn(lookupEnv))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -639,7 +676,7 @@ func resolveVolumePath(volume types.ServiceVolumeConfig, workingDir string, look
|
||||
// Note that this is not required for Docker for Windows when specifying
|
||||
// a local Windows path, because Docker for Windows translates the Windows
|
||||
// path into a valid path within the VM.
|
||||
if !path.IsAbs(filePath) && !isAbs(filePath) {
|
||||
if !paths.IsAbs(filePath) && !isAbs(filePath) {
|
||||
filePath = absPath(workingDir, filePath)
|
||||
}
|
||||
volume.Source = filePath
|
||||
@ -793,10 +830,8 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi
|
||||
logrus.Warnf("%[1]s %[2]s: %[1]s.external.name is deprecated in favor of %[1]s.name", objType, name)
|
||||
obj.Name = obj.External.Name
|
||||
obj.External.Name = ""
|
||||
} else {
|
||||
if obj.Name == "" {
|
||||
obj.Name = name
|
||||
}
|
||||
} else if obj.Name == "" {
|
||||
obj.Name = name
|
||||
}
|
||||
// if not "external: true"
|
||||
case obj.Driver != "":
|
||||
@ -928,7 +963,7 @@ func cleanTarget(target string) string {
|
||||
if target == "" {
|
||||
return ""
|
||||
}
|
||||
return path.Clean(target)
|
||||
return paths.Clean(target)
|
||||
}
|
||||
|
||||
var transformBuildConfig TransformerFunc = func(data interface{}) (interface{}, error) {
|
||||
|
4
vendor/github.com/compose-spec/compose-go/loader/merge.go
generated
vendored
4
vendor/github.com/compose-spec/compose-go/loader/merge.go
generated
vendored
@ -299,7 +299,7 @@ func mergeLoggingConfig(dst, src reflect.Value) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint: unparam
|
||||
//nolint: unparam
|
||||
func mergeUlimitsConfig(dst, src reflect.Value) error {
|
||||
if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() {
|
||||
dst.Elem().Set(src.Elem())
|
||||
@ -307,7 +307,7 @@ func mergeUlimitsConfig(dst, src reflect.Value) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// nolint: unparam
|
||||
//nolint: unparam
|
||||
func mergeServiceNetworkConfig(dst, src reflect.Value) error {
|
||||
if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() {
|
||||
dst.Elem().FieldByName("Aliases").Set(src.Elem().FieldByName("Aliases"))
|
||||
|
14
vendor/github.com/compose-spec/compose-go/loader/normalize.go
generated
vendored
14
vendor/github.com/compose-spec/compose-go/loader/normalize.go
generated
vendored
@ -28,7 +28,7 @@ import (
|
||||
)
|
||||
|
||||
// normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults
|
||||
func normalize(project *types.Project, resolvePaths bool) error {
|
||||
func normalize(project *types.Project, resolvePaths bool, separator string) error {
|
||||
absWorkingDir, err := filepath.Abs(project.WorkingDir)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -110,7 +110,7 @@ func normalize(project *types.Project, resolvePaths bool) error {
|
||||
project.Services[i] = s
|
||||
}
|
||||
|
||||
setNameFromKey(project)
|
||||
setNameFromKey(project, separator)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -143,31 +143,31 @@ func absComposeFiles(composeFiles []string) ([]string, error) {
|
||||
}
|
||||
|
||||
// Resources with no explicit name are actually named by their key in map
|
||||
func setNameFromKey(project *types.Project) {
|
||||
func setNameFromKey(project *types.Project, separator string) {
|
||||
for i, n := range project.Networks {
|
||||
if n.Name == "" {
|
||||
n.Name = fmt.Sprintf("%s_%s", project.Name, i)
|
||||
n.Name = fmt.Sprintf("%s%s%s", project.Name, separator, i)
|
||||
project.Networks[i] = n
|
||||
}
|
||||
}
|
||||
|
||||
for i, v := range project.Volumes {
|
||||
if v.Name == "" {
|
||||
v.Name = fmt.Sprintf("%s_%s", project.Name, i)
|
||||
v.Name = fmt.Sprintf("%s%s%s", project.Name, separator, i)
|
||||
project.Volumes[i] = v
|
||||
}
|
||||
}
|
||||
|
||||
for i, c := range project.Configs {
|
||||
if c.Name == "" {
|
||||
c.Name = fmt.Sprintf("%s_%s", project.Name, i)
|
||||
c.Name = fmt.Sprintf("%s%s%s", project.Name, separator, i)
|
||||
project.Configs[i] = c
|
||||
}
|
||||
}
|
||||
|
||||
for i, s := range project.Secrets {
|
||||
if s.Name == "" {
|
||||
s.Name = fmt.Sprintf("%s_%s", project.Name, i)
|
||||
s.Name = fmt.Sprintf("%s%s%s", project.Name, separator, i)
|
||||
project.Secrets[i] = s
|
||||
}
|
||||
}
|
||||
|
15
vendor/github.com/compose-spec/compose-go/loader/validate.go
generated
vendored
15
vendor/github.com/compose-spec/compose-go/loader/validate.go
generated
vendored
@ -38,6 +38,12 @@ func checkConsistency(project *types.Project) error {
|
||||
}
|
||||
}
|
||||
|
||||
for dependedService := range s.DependsOn {
|
||||
if _, err := project.GetService(dependedService); err != nil {
|
||||
return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q depends on undefined service %s", s.Name, dependedService))
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s.NetworkMode, types.ServicePrefix) {
|
||||
serviceName := s.NetworkMode[len(types.ServicePrefix):]
|
||||
if _, err := project.GetServices(serviceName); err != nil {
|
||||
@ -46,12 +52,9 @@ func checkConsistency(project *types.Project) error {
|
||||
}
|
||||
|
||||
for _, volume := range s.Volumes {
|
||||
switch volume.Type {
|
||||
case types.VolumeTypeVolume:
|
||||
if volume.Source != "" { // non anonymous volumes
|
||||
if _, ok := project.Volumes[volume.Source]; !ok {
|
||||
return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined volume %s", s.Name, volume.Source))
|
||||
}
|
||||
if volume.Type == types.VolumeTypeVolume && volume.Source != "" { // non anonymous volumes
|
||||
if _, ok := project.Volumes[volume.Source]; !ok {
|
||||
return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined volume %s", s.Name, volume.Source))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
vendor/github.com/compose-spec/compose-go/loader/windows_path.go
generated
vendored
2
vendor/github.com/compose-spec/compose-go/loader/windows_path.go
generated
vendored
@ -44,7 +44,7 @@ func isAbs(path string) (b bool) {
|
||||
|
||||
// volumeNameLen returns length of the leading volume name on Windows.
|
||||
// It returns 0 elsewhere.
|
||||
// nolint: gocyclo
|
||||
//nolint: gocyclo
|
||||
func volumeNameLen(path string) int {
|
||||
if len(path) < 2 {
|
||||
return 0
|
||||
|
140
vendor/github.com/compose-spec/compose-go/template/template.go
generated
vendored
140
vendor/github.com/compose-spec/compose-go/template/template.go
generated
vendored
@ -19,6 +19,7 @@ package template
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -27,7 +28,7 @@ import (
|
||||
var delimiter = "\\$"
|
||||
var substitutionNamed = "[_a-z][_a-z0-9]*"
|
||||
|
||||
var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-?](.*}|[^}]*))?"
|
||||
var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-+?](.*}|[^}]*))?"
|
||||
|
||||
var patternString = fmt.Sprintf(
|
||||
"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
|
||||
@ -60,10 +61,12 @@ type SubstituteFunc func(string, Mapping) (string, bool, error)
|
||||
// SubstituteWith substitute variables in the string with their values.
|
||||
// It accepts additional substitute function.
|
||||
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
|
||||
if len(subsFuncs) == 0 {
|
||||
subsFuncs = getDefaultSortedSubstitutionFunctions(template)
|
||||
}
|
||||
var err error
|
||||
|
||||
if len(subsFuncs) == 0 {
|
||||
_, subsFunc := getSubstitutionFunctionForTemplate(template)
|
||||
subsFuncs = []SubstituteFunc{subsFunc}
|
||||
}
|
||||
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
|
||||
closingBraceIndex := getFirstBraceClosingIndex(substring)
|
||||
rest := ""
|
||||
@ -121,23 +124,31 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
|
||||
return result, err
|
||||
}
|
||||
|
||||
func getDefaultSortedSubstitutionFunctions(template string, fns ...SubstituteFunc) []SubstituteFunc {
|
||||
hyphenIndex := strings.IndexByte(template, '-')
|
||||
questionIndex := strings.IndexByte(template, '?')
|
||||
if hyphenIndex < 0 || hyphenIndex > questionIndex {
|
||||
return []SubstituteFunc{
|
||||
requiredNonEmpty,
|
||||
required,
|
||||
softDefault,
|
||||
hardDefault,
|
||||
func getSubstitutionFunctionForTemplate(template string) (string, SubstituteFunc) {
|
||||
interpolationMapping := []struct {
|
||||
string
|
||||
SubstituteFunc
|
||||
}{
|
||||
{":?", requiredErrorWhenEmptyOrUnset},
|
||||
{"?", requiredErrorWhenUnset},
|
||||
{":-", defaultWhenEmptyOrUnset},
|
||||
{"-", defaultWhenUnset},
|
||||
{":+", defaultWhenNotEmpty},
|
||||
{"+", defaultWhenSet},
|
||||
}
|
||||
sort.Slice(interpolationMapping, func(i, j int) bool {
|
||||
idxI := strings.Index(template, interpolationMapping[i].string)
|
||||
idxJ := strings.Index(template, interpolationMapping[j].string)
|
||||
if idxI < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return []SubstituteFunc{
|
||||
softDefault,
|
||||
hardDefault,
|
||||
requiredNonEmpty,
|
||||
required,
|
||||
}
|
||||
if idxJ < 0 {
|
||||
return true
|
||||
}
|
||||
return idxI < idxJ
|
||||
})
|
||||
|
||||
return interpolationMapping[0].string, interpolationMapping[0].SubstituteFunc
|
||||
}
|
||||
|
||||
func getFirstBraceClosingIndex(s string) int {
|
||||
@ -203,9 +214,10 @@ func recurseExtract(value interface{}, pattern *regexp.Regexp) map[string]Variab
|
||||
}
|
||||
|
||||
type Variable struct {
|
||||
Name string
|
||||
DefaultValue string
|
||||
Required bool
|
||||
Name string
|
||||
DefaultValue string
|
||||
PresenceValue string
|
||||
Required bool
|
||||
}
|
||||
|
||||
func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, bool) {
|
||||
@ -229,6 +241,7 @@ func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, boo
|
||||
}
|
||||
name := val
|
||||
var defaultValue string
|
||||
var presenceValue string
|
||||
var required bool
|
||||
switch {
|
||||
case strings.Contains(val, ":?"):
|
||||
@ -241,37 +254,52 @@ func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, boo
|
||||
name, defaultValue = partition(val, ":-")
|
||||
case strings.Contains(val, "-"):
|
||||
name, defaultValue = partition(val, "-")
|
||||
case strings.Contains(val, ":+"):
|
||||
name, presenceValue = partition(val, ":+")
|
||||
case strings.Contains(val, "+"):
|
||||
name, presenceValue = partition(val, "+")
|
||||
}
|
||||
values = append(values, Variable{
|
||||
Name: name,
|
||||
DefaultValue: defaultValue,
|
||||
Required: required,
|
||||
Name: name,
|
||||
DefaultValue: defaultValue,
|
||||
PresenceValue: presenceValue,
|
||||
Required: required,
|
||||
})
|
||||
}
|
||||
return values, len(values) > 0
|
||||
}
|
||||
|
||||
// Soft default (fall back if unset or empty)
|
||||
func softDefault(substitution string, mapping Mapping) (string, bool, error) {
|
||||
sep := ":-"
|
||||
if !strings.Contains(substitution, sep) {
|
||||
return "", false, nil
|
||||
}
|
||||
name, defaultValue := partition(substitution, sep)
|
||||
defaultValue, err := Substitute(defaultValue, mapping)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
value, ok := mapping(name)
|
||||
if !ok || value == "" {
|
||||
return defaultValue, true, nil
|
||||
}
|
||||
return value, true, nil
|
||||
func defaultWhenEmptyOrUnset(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withDefaultWhenAbsence(substitution, mapping, true)
|
||||
}
|
||||
|
||||
// Hard default (fall back if-and-only-if empty)
|
||||
func hardDefault(substitution string, mapping Mapping) (string, bool, error) {
|
||||
sep := "-"
|
||||
func defaultWhenUnset(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withDefaultWhenAbsence(substitution, mapping, false)
|
||||
}
|
||||
|
||||
func defaultWhenNotEmpty(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withDefaultWhenPresence(substitution, mapping, true)
|
||||
}
|
||||
|
||||
func defaultWhenSet(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withDefaultWhenPresence(substitution, mapping, false)
|
||||
}
|
||||
|
||||
func requiredErrorWhenEmptyOrUnset(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withRequired(substitution, mapping, ":?", func(v string) bool { return v != "" })
|
||||
}
|
||||
|
||||
func requiredErrorWhenUnset(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withRequired(substitution, mapping, "?", func(_ string) bool { return true })
|
||||
}
|
||||
|
||||
func withDefaultWhenPresence(substitution string, mapping Mapping, notEmpty bool) (string, bool, error) {
|
||||
sep := "+"
|
||||
if notEmpty {
|
||||
sep = ":+"
|
||||
}
|
||||
if !strings.Contains(substitution, sep) {
|
||||
return "", false, nil
|
||||
}
|
||||
@ -281,18 +309,30 @@ func hardDefault(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return "", false, err
|
||||
}
|
||||
value, ok := mapping(name)
|
||||
if !ok {
|
||||
if ok && (!notEmpty || (notEmpty && value != "")) {
|
||||
return defaultValue, true, nil
|
||||
}
|
||||
return value, true, nil
|
||||
}
|
||||
|
||||
func requiredNonEmpty(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withRequired(substitution, mapping, ":?", func(v string) bool { return v != "" })
|
||||
}
|
||||
|
||||
func required(substitution string, mapping Mapping) (string, bool, error) {
|
||||
return withRequired(substitution, mapping, "?", func(_ string) bool { return true })
|
||||
func withDefaultWhenAbsence(substitution string, mapping Mapping, emptyOrUnset bool) (string, bool, error) {
|
||||
sep := "-"
|
||||
if emptyOrUnset {
|
||||
sep = ":-"
|
||||
}
|
||||
if !strings.Contains(substitution, sep) {
|
||||
return "", false, nil
|
||||
}
|
||||
name, defaultValue := partition(substitution, sep)
|
||||
defaultValue, err := Substitute(defaultValue, mapping)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
value, ok := mapping(name)
|
||||
if !ok || (emptyOrUnset && value == "") {
|
||||
return defaultValue, true, nil
|
||||
}
|
||||
return value, true, nil
|
||||
}
|
||||
|
||||
func withRequired(substitution string, mapping Mapping, sep string, valid func(string) bool) (string, bool, error) {
|
||||
|
12
vendor/github.com/compose-spec/compose-go/types/project.go
generated
vendored
12
vendor/github.com/compose-spec/compose-go/types/project.go
generated
vendored
@ -23,7 +23,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
"github.com/opencontainers/go-digest"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@ -142,18 +142,19 @@ func (p Project) WithServices(names []string, fn ServiceFunc) error {
|
||||
return p.withServices(names, fn, map[string]bool{})
|
||||
}
|
||||
|
||||
func (p Project) withServices(names []string, fn ServiceFunc, done map[string]bool) error {
|
||||
func (p Project) withServices(names []string, fn ServiceFunc, seen map[string]bool) error {
|
||||
services, err := p.GetServices(names...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, service := range services {
|
||||
if done[service.Name] {
|
||||
if seen[service.Name] {
|
||||
continue
|
||||
}
|
||||
seen[service.Name] = true
|
||||
dependencies := service.GetDependencies()
|
||||
if len(dependencies) > 0 {
|
||||
err := p.withServices(dependencies, fn, done)
|
||||
err := p.withServices(dependencies, fn, seen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -161,7 +162,6 @@ func (p Project) withServices(names []string, fn ServiceFunc, done map[string]bo
|
||||
if err := fn(service); err != nil {
|
||||
return err
|
||||
}
|
||||
done[service.Name] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -311,7 +311,7 @@ func (p *Project) ForServices(names []string) error {
|
||||
}
|
||||
|
||||
// ResolveImages updates services images to include digest computed by a resolver function
|
||||
func (p *Project) ResolveImages(resolver func(named reference.Named) (digest.Digest, error)) error {
|
||||
func (p *Project) ResolveImages(resolver func(named reference.Named) (godigest.Digest, error)) error {
|
||||
eg := errgroup.Group{}
|
||||
for i, s := range p.Services {
|
||||
idx := i
|
||||
|
35
vendor/github.com/compose-spec/compose-go/types/types.go
generated
vendored
35
vendor/github.com/compose-spec/compose-go/types/types.go
generated
vendored
@ -204,26 +204,26 @@ func (s *ServiceConfig) NetworksByPriority() []string {
|
||||
}
|
||||
|
||||
const (
|
||||
//PullPolicyAlways always pull images
|
||||
// PullPolicyAlways always pull images
|
||||
PullPolicyAlways = "always"
|
||||
//PullPolicyNever never pull images
|
||||
// PullPolicyNever never pull images
|
||||
PullPolicyNever = "never"
|
||||
//PullPolicyIfNotPresent pull missing images
|
||||
// PullPolicyIfNotPresent pull missing images
|
||||
PullPolicyIfNotPresent = "if_not_present"
|
||||
//PullPolicyMissing pull missing images
|
||||
// PullPolicyMissing pull missing images
|
||||
PullPolicyMissing = "missing"
|
||||
//PullPolicyBuild force building images
|
||||
// PullPolicyBuild force building images
|
||||
PullPolicyBuild = "build"
|
||||
)
|
||||
|
||||
const (
|
||||
//RestartPolicyAlways always restart the container if it stops
|
||||
// RestartPolicyAlways always restart the container if it stops
|
||||
RestartPolicyAlways = "always"
|
||||
//RestartPolicyOnFailure restart the container if it exits due to an error
|
||||
// RestartPolicyOnFailure restart the container if it exits due to an error
|
||||
RestartPolicyOnFailure = "on-failure"
|
||||
//RestartPolicyNo do not automatically restart the container
|
||||
// RestartPolicyNo do not automatically restart the container
|
||||
RestartPolicyNo = "no"
|
||||
//RestartPolicyUnlessStopped always restart the container unless the container is stopped (manually or otherwise)
|
||||
// RestartPolicyUnlessStopped always restart the container unless the container is stopped (manually or otherwise)
|
||||
RestartPolicyUnlessStopped = "unless-stopped"
|
||||
)
|
||||
|
||||
@ -275,8 +275,8 @@ func (s ServiceConfig) GetDependencies() []string {
|
||||
|
||||
type set map[string]struct{}
|
||||
|
||||
func (s set) append(strings ...string) {
|
||||
for _, str := range strings {
|
||||
func (s set) append(strs ...string) {
|
||||
for _, str := range strs {
|
||||
s[str] = struct{}{}
|
||||
}
|
||||
}
|
||||
@ -459,9 +459,9 @@ func (s SSHKey) MarshalYAML() (interface{}, error) {
|
||||
// MarshalJSON makes SSHKey implement json.Marshaller
|
||||
func (s SSHKey) MarshalJSON() ([]byte, error) {
|
||||
if s.Path == "" {
|
||||
return []byte(fmt.Sprintf(`"%s"`, s.ID)), nil
|
||||
return []byte(fmt.Sprintf(`%q`, s.ID)), nil
|
||||
}
|
||||
return []byte(fmt.Sprintf(`"%s": %s`, s.ID, s.Path)), nil
|
||||
return []byte(fmt.Sprintf(`%q: %s`, s.ID, s.Path)), nil
|
||||
}
|
||||
|
||||
// MappingWithColon is a mapping type that can be converted from a list of
|
||||
@ -617,10 +617,11 @@ type PlacementPreferences struct {
|
||||
|
||||
// ServiceNetworkConfig is the network configuration for a service
|
||||
type ServiceNetworkConfig struct {
|
||||
Priority int `yaml:",omitempty" json:"priotirt,omitempty"`
|
||||
Aliases []string `yaml:",omitempty" json:"aliases,omitempty"`
|
||||
Ipv4Address string `mapstructure:"ipv4_address" yaml:"ipv4_address,omitempty" json:"ipv4_address,omitempty"`
|
||||
Ipv6Address string `mapstructure:"ipv6_address" yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"`
|
||||
Priority int `yaml:",omitempty" json:"priority,omitempty"`
|
||||
Aliases []string `yaml:",omitempty" json:"aliases,omitempty"`
|
||||
Ipv4Address string `mapstructure:"ipv4_address" yaml:"ipv4_address,omitempty" json:"ipv4_address,omitempty"`
|
||||
Ipv6Address string `mapstructure:"ipv6_address" yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"`
|
||||
LinkLocalIPs []string `mapstructure:"link_local_ips" yaml:"link_local_ips,omitempty" json:"link_local_ips,omitempty"`
|
||||
|
||||
Extensions map[string]interface{} `yaml:",inline" json:"-"`
|
||||
}
|
||||
|
Reference in New Issue
Block a user