diff --git a/README.md b/README.md index 655c79b0cd165..9e3f39e45888e 100644 --- a/README.md +++ b/README.md @@ -215,9 +215,11 @@ By default, the build result and intermediate cache will only remain internally > Color Output Controls > -> `buildctl` has support for modifying the colors that are used to output information to the terminal. You can set the environment varliable `BUILDKIT_COLORS` to something like `run=blue;cancel=yellow;warn=orange;error=red` to set the colors that you would like to use. You can also set `NO_COLOR` to disable colorized output. +> `buildctl` has support for modifying the colors that are used to output information to the terminal. You can set the environment variable `BUILDKIT_COLORS` to something like `run=blue;cancel=yellow;warn=orange;error=123,12,0` to set the colors that you would like to use. You can also set `NO_COLOR` to disable colorized output. > -> - [A list of the supported colors](https://github.com/moby/buildkit/blob/master/util/progress/progressui/colors.go). +> Parsing errors will be reported, but ignored. This will result in default color values being used where needed. +> +> - [The list of pre-defined colors](https://github.com/moby/buildkit/blob/master/util/progress/progressui/colors.go). #### Image/Registry diff --git a/util/progress/progressui/colors.go b/util/progress/progressui/colors.go index c750223bd6b09..a9e1673897b75 100644 --- a/util/progress/progressui/colors.go +++ b/util/progress/progressui/colors.go @@ -1,15 +1,14 @@ package progressui import ( - "fmt" - "regexp" + "encoding/csv" + "strconv" "strings" "github.com/morikuni/aec" + "github.com/sirupsen/logrus" ) -var rex = regexp.MustCompile(`(\w+)=(\w+)`) - var termColorMap = map[string]aec.ANSI{ "default": aec.DefaultF, @@ -35,25 +34,109 @@ var termColorMap = map[string]aec.ANSI{ } func setUserDefinedTermColors(colorsEnv string) { - data := rex.FindAllStringSubmatch(colorsEnv, -1) - for _, kv := range data { - k := kv[1] - v := kv[2] - // debug - fmt.Print(k, "=", v) + fields := readBuildkitColorsEnv(colorsEnv) + if fields == nil { + return + } + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + if len(parts) != 2 { + logrus.Warnf("Could not parse BUILDKIT_COLORS component: %s", strings.Join(parts, "=")) + continue + } + if strings.Contains(parts[1], "=") { + logrus.Warnf("Could not parse BUILDKIT_COLORS component: %s", strings.Join(parts, "=")) + continue + } + k := strings.ToLower(parts[0]) + v := parts[1] c, ok := termColorMap[strings.ToLower(v)] if ok { - key := strings.ToLower(k) - switch key { - case "run": - colorRun = c - case "cancel": - colorCancel = c - case "error": - colorError = c - case "warning": - colorWarning = c + parseKeys(k, c) + } else { + if strings.Contains(v, ",") { + rgbFields := readRGB(v) + if rgbFields == nil { + continue + } + ok = isValidRGB(rgbFields) + if ok { + p1, _ := strconv.Atoi(rgbFields[0]) + p2, _ := strconv.Atoi(rgbFields[1]) + p3, _ := strconv.Atoi(rgbFields[2]) + c = aec.Color8BitF(aec.NewRGB8Bit(uint8(p1), uint8(p2), uint8(p3))) + parseKeys(k, c) + } + } else { + logrus.Warnf("Unknown color value found in BUILDKIT_COLORS: %s=%s", k, v) } } } } + +func readBuildkitColorsEnv(colorsEnv string) []string { + csvReader := csv.NewReader(strings.NewReader(colorsEnv)) + csvReader.Comma = ':' // Use colon instead of comma + fields, err := csvReader.Read() + if err != nil { + logrus.WithError(err).Warnf("Could not parse BUILDKIT_COLORS. Falling back to defaults.") + return nil + } + return fields +} + +func readRGB(v string) []string { + csvReader := csv.NewReader(strings.NewReader(v)) + csvReader.Comma = ',' // Use colon instead of comma + fields, err := csvReader.Read() + if err != nil { + logrus.WithError(err).Warnf("Could not parse value %s as valid RGB color. Ignoring.", v) + return nil + } + if len(fields) != 3 { + logrus.Warnf("Could not parse value %s as valid RGB color. Ignoring.", v) + return nil + } + return fields +} + +func parseKeys(k string, c aec.ANSI) { + key := strings.ToLower(k) + switch key { + case "run": + colorRun = c + case "cancel": + colorCancel = c + case "error": + colorError = c + case "warning": + colorWarning = c + default: + logrus.Warnf("Unknown key found in BUILDKIT_COLORS (expected: run, cancel, error, or warning): %s", k) + } +} + +func isValidRGB(s []string) bool { + for _, n := range s { + num, err := strconv.Atoi(n) + if err != nil { + logrus.Warnf("A field in BUILDKIT_COLORS appears to contain an RGB value that is not an integer: %s", strings.Join(s, ",")) + return false + } + ok := isValidRGBValue(num) + if ok { + continue + } else { + logrus.Warnf("A field in BUILDKIT_COLORS appears to contain an RGB value that is not within the valid range of 0-255: %s", strings.Join(s, ",")) + return false + } + } + return true +} + +func isValidRGBValue(i int) bool { + if (i >= 0) && (i <= 255) { + return true + } + return false +}