Skip to content

Commit

Permalink
Merge pull request #2954 from spkane/terminal-colors
Browse files Browse the repository at this point in the history
Make terminal colors configurable
  • Loading branch information
tonistiigi committed Jul 20, 2022
2 parents 7273751 + 509444f commit ebb1e82
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 30 deletions.
22 changes: 17 additions & 5 deletions README.md
@@ -1,6 +1,6 @@
[![asciicinema example](https://asciinema.org/a/gPEIEo1NzmDTUu2bEPsUboqmU.png)](https://asciinema.org/a/gPEIEo1NzmDTUu2bEPsUboqmU)

# BuildKit
# BuildKit <!-- omit in toc -->

[![GoDoc](https://godoc.org/github.com/moby/buildkit?status.svg)](https://godoc.org/github.com/moby/buildkit/client/llb)
[![Build Status](https://github.com/moby/buildkit/workflows/build/badge.svg)](https://github.com/moby/buildkit/actions?query=workflow%3Abuild)
Expand Down Expand Up @@ -45,11 +45,11 @@ Join `#buildkit` channel on [Docker Community Slack](http://dockr.ly/slack)

- [Used by](#used-by)
- [Quick start](#quick-start)
- [Starting the `buildkitd` daemon:](#starting-the-buildkitd-daemon)
- [Starting the `buildkitd` daemon](#starting-the-buildkitd-daemon)
- [Exploring LLB](#exploring-llb)
- [Exploring Dockerfiles](#exploring-dockerfiles)
- [Building a Dockerfile with `buildctl`](#building-a-dockerfile-with-buildctl)
- [Building a Dockerfile using external frontend:](#building-a-dockerfile-using-external-frontend)
- [Building a Dockerfile using external frontend](#building-a-dockerfile-using-external-frontend)
- [Output](#output)
- [Image/Registry](#imageregistry)
- [Local directory](#local-directory)
Expand All @@ -76,6 +76,8 @@ Join `#buildkit` channel on [Docker Community Slack](http://dockr.ly/slack)
- [Opentracing support](#opentracing-support)
- [Running BuildKit without root privileges](#running-buildkit-without-root-privileges)
- [Building multi-platform images](#building-multi-platform-images)
- [Configuring `buildctl`](#configuring-buildctl)
- [Color Output Controls](#color-output-controls)
- [Contributing](#contributing)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand Down Expand Up @@ -121,7 +123,7 @@ $ brew install buildkit

To build BuildKit from source, see [`.github/CONTRIBUTING.md`](./.github/CONTRIBUTING.md).

### Starting the `buildkitd` daemon:
### Starting the `buildkitd` daemon

You need to run `buildkitd` as the root user on the host.

Expand Down Expand Up @@ -193,7 +195,7 @@ buildctl build \

`--local` exposes local source files from client to the builder. `context` and `dockerfile` are the names Dockerfile frontend looks for build context and Dockerfile location.

#### Building a Dockerfile using external frontend:
#### Building a Dockerfile using external frontend

External versions of the Dockerfile frontend are pushed to https://hub.docker.com/r/docker/dockerfile-upstream and https://hub.docker.com/r/docker/dockerfile and can be used with the gateway frontend. The source for the external frontend is currently located in `./frontend/dockerfile/cmd/dockerfile-frontend` but will move out of this repository in the future ([#163](https://github.com/moby/buildkit/issues/163)). For automatic build from master branch of this repository `docker/dockerfile-upstream:master` or `docker/dockerfile-upstream:master-labs` image can be used.

Expand Down Expand Up @@ -669,6 +671,16 @@ Please refer to [`docs/rootless.md`](docs/rootless.md).

Please refer to [`docs/multi-platform.md`](docs/multi-platform.md).

### Configuring `buildctl`

#### Color Output Controls

`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. Setting `NO_COLOR` to anything will disable any colorized output as recommended by [no-color.org](https://no-color.org/).

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).

## Contributing

Want to contribute to BuildKit? Awesome! You can find information about contributing to this project in the [CONTRIBUTING.md](/.github/CONTRIBUTING.md)
135 changes: 135 additions & 0 deletions util/progress/progressui/colors.go
@@ -0,0 +1,135 @@
package progressui

import (
"encoding/csv"
"errors"
"strconv"
"strings"

"github.com/morikuni/aec"
"github.com/sirupsen/logrus"
)

var termColorMap = map[string]aec.ANSI{
"default": aec.DefaultF,

"black": aec.BlackF,
"blue": aec.BlueF,
"cyan": aec.CyanF,
"green": aec.GreenF,
"magenta": aec.MagentaF,
"red": aec.RedF,
"white": aec.WhiteF,
"yellow": aec.YellowF,

"light-black": aec.LightBlackF,
"light-blue": aec.LightBlueF,
"light-cyan": aec.LightCyanF,
"light-green": aec.LightGreenF,
"light-magenta": aec.LightMagentaF,
"light-red": aec.LightRedF,
"light-white": aec.LightWhiteF,
"light-yellow": aec.LightYellowF,
}

func setUserDefinedTermColors(colorsEnv string) {
fields := readBuildkitColorsEnv(colorsEnv)
if fields == nil {
return
}
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
if len(parts) != 2 || strings.Contains(parts[1], "=") {
err := errors.New("A valid entry must have exactly two fields")
logrus.WithError(err).Warnf("Could not parse BUILDKIT_COLORS component: %s", field)
continue
}
k := strings.ToLower(parts[0])
v := parts[1]
if c, ok := termColorMap[strings.ToLower(v)]; ok {
parseKeys(k, c)
} else if strings.Contains(v, ",") {
if c := readRGB(v); c != nil {
parseKeys(k, c)
}
} else {
err := errors.New("Colors must be a name from the pre-defined list or a valid 3-part RGB value")
logrus.WithError(err).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 = ':'
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) aec.ANSI {
csvReader := csv.NewReader(strings.NewReader(v))
fields, err := csvReader.Read()
if err != nil {
logrus.WithError(err).Warnf("Could not parse value %s as valid comma-separated RGB color. Ignoring.", v)
return nil
}
if len(fields) != 3 {
err = errors.New("A valid RGB color must have three fields")
logrus.WithError(err).Warnf("Could not parse value %s as valid RGB color. Ignoring.", v)
return nil
}
ok := isValidRGB(fields)
if ok {
p1, _ := strconv.Atoi(fields[0])
p2, _ := strconv.Atoi(fields[1])
p3, _ := strconv.Atoi(fields[2])
c := aec.Color8BitF(aec.NewRGB8Bit(uint8(p1), uint8(p2), uint8(p3)))
return c
}
return nil
}

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
}
8 changes: 7 additions & 1 deletion util/progress/progressui/display.go
Expand Up @@ -108,6 +108,7 @@ type job struct {
name string
status string
hasError bool
hasWarning bool // This is currently unused, but it's here for future use.
isCanceled bool
vertex *vertex
showTerm bool
Expand Down Expand Up @@ -829,8 +830,13 @@ func (disp *display) print(d displayInfo, width, height int, all bool) {
color = colorCancel
} else if j.hasError {
color = colorError
} else if j.hasWarning {
// This is currently unused, but it's here for future use.
color = colorWarning
}
if color != nil {
out = aec.Apply(out, color)
}
out = aec.Apply(out, color)
}
fmt.Fprint(disp.c, out)
lineCount++
Expand Down
37 changes: 37 additions & 0 deletions util/progress/progressui/init.go
@@ -0,0 +1,37 @@
package progressui

import (
"os"
"runtime"

"github.com/morikuni/aec"
)

var colorRun aec.ANSI
var colorCancel aec.ANSI
var colorWarning aec.ANSI
var colorError aec.ANSI

func init() {
// As recommended on https://no-color.org/
if _, ok := os.LookupEnv("NO_COLOR"); ok {
// nil values will result in no ANSI color codes being emitted.
return
} else if runtime.GOOS == "windows" {
colorRun = termColorMap["cyan"]
colorCancel = termColorMap["yellow"]
colorWarning = termColorMap["yellow"]
colorError = termColorMap["red"]
} else {
colorRun = termColorMap["blue"]
colorCancel = termColorMap["yellow"]
colorWarning = termColorMap["yellow"]
colorError = termColorMap["red"]
}

// Loosely based on the standard set by Linux LS_COLORS.
if _, ok := os.LookupEnv("BUILDKIT_COLORS"); ok {
envColorString := os.Getenv("BUILDKIT_COLORS")
setUserDefinedTermColors(envColorString)
}
}
12 changes: 0 additions & 12 deletions util/progress/progressui/term.go

This file was deleted.

12 changes: 0 additions & 12 deletions util/progress/progressui/term_windows.go

This file was deleted.

0 comments on commit ebb1e82

Please sign in to comment.