Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
|
92874977b3 | |
|
567d8cdffe | |
|
a4097ee1c6 | |
|
0aca9d5c23 | |
|
21efb39a9d | |
|
736a1f1b58 | |
|
1274da091a | |
|
ea54ed62ba | |
|
4f362c97f6 | |
|
f5f9efa459 | |
|
3dc095b4ce | |
|
76a1997b82 | |
|
c010e10f7c | |
|
c605780c95 | |
|
336dd7cd10 |
|
@ -0,0 +1,20 @@
|
||||||
|
on: [push, pull_request]
|
||||||
|
name: Check
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version: [1.15.x]
|
||||||
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
- name: Install go-sumtype
|
||||||
|
run: go get github.com/BurntSushi/go-sumtype
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Test
|
||||||
|
run: make test
|
17
CHANGES.md
17
CHANGES.md
|
@ -1,5 +1,17 @@
|
||||||
**Experimental**: This is a new format for release notes. The format and availability is subject to change.
|
**Experimental**: This is a new format for release notes. The format and availability is subject to change.
|
||||||
|
|
||||||
|
## 2020.5.0
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
- It is now possible to run the same tunnel using more than one `cloudflared` instance. This is a server-side change and
|
||||||
|
is compatible with any client version that uses Named Tunnels.
|
||||||
|
|
||||||
|
To get started, visit our [developer documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/deploy-cloudflared-replicas).
|
||||||
|
- `cloudflared tunnel ingress validate` will now warn about unused keys in your config file. This is helpful for
|
||||||
|
detecting typos in your config.
|
||||||
|
- If `cloudflared` detects it is running inside a Linux container, it will limit itself to use only the number of CPUs
|
||||||
|
the pod has been granted, instead of trying to use every CPU available.
|
||||||
|
|
||||||
## 2021.4.0
|
## 2021.4.0
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
@ -31,7 +43,7 @@
|
||||||
### Improvements
|
### Improvements
|
||||||
|
|
||||||
- Tunnel create command, as well as, running ad-hoc tunnels using `cloudflared tunnel -name NAME`, will not overwrite
|
- Tunnel create command, as well as, running ad-hoc tunnels using `cloudflared tunnel -name NAME`, will not overwrite
|
||||||
existing files when writing tunnel credentials.
|
existing files when writing tunnel credentials.
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
@ -77,7 +89,7 @@ ingress:
|
||||||
ports: [80, 443]
|
ports: [80, 443]
|
||||||
allow: true
|
allow: true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
|
||||||
|
@ -129,4 +141,3 @@ ingress:
|
||||||
|
|
||||||
- The maximum number of upstream connections is now limited by default which should fix reported issues of cloudflared
|
- The maximum number of upstream connections is now limited by default which should fix reported issues of cloudflared
|
||||||
exhausting CPU usage when faced with connectivity issues.
|
exhausting CPU usage when faced with connectivity issues.
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# use a builder image for building cloudflare
|
# use a builder image for building cloudflare
|
||||||
ARG TARGET_GOOS
|
ARG TARGET_GOOS
|
||||||
ARG TARGET_GOARCH
|
ARG TARGET_GOARCH
|
||||||
FROM golang:1.15.7 as builder
|
FROM golang:1.16.4 as builder
|
||||||
ENV GO111MODULE=on \
|
ENV GO111MODULE=on \
|
||||||
CGO_ENABLED=0 \
|
CGO_ENABLED=0 \
|
||||||
TARGET_GOOS=${TARGET_GOOS} \
|
TARGET_GOOS=${TARGET_GOOS} \
|
||||||
|
|
|
@ -1,3 +1,27 @@
|
||||||
|
2021.5.3
|
||||||
|
- 2021-05-13 TUN-4411: Fix Go version
|
||||||
|
|
||||||
|
2021.5.2
|
||||||
|
- 2021-05-04 Setup a Github action for checking the cloudflared build
|
||||||
|
|
||||||
|
2021.5.1
|
||||||
|
- 2021-05-10 TUN-4342: Fix false positive warning about unused hostname property
|
||||||
|
- 2021-05-10 Release 2021.5.0
|
||||||
|
|
||||||
|
2021.5.0
|
||||||
|
- 2021-05-10 TUN-4384: Silence log from automaxprocs
|
||||||
|
- 2021-05-10 AUTH-3537: AUDs in JWTs are now always arrays
|
||||||
|
- 2021-05-10 Update changelog for 2021.5.0
|
||||||
|
- 2021-05-03 TUN-4343: Fix broken build by setting debug field correctly
|
||||||
|
- 2021-05-06 TUN-4356: Set AUTOMAXPROCS to the CPU limit when running in a Linux container
|
||||||
|
- 2021-05-06 TUN-4357: Bump Go to 1.16
|
||||||
|
- 2021-05-06 TUN-4359: Warn about unused keys in 'tunnel ingress validate'
|
||||||
|
- 2021-04-30 debug: log host / path
|
||||||
|
- 2021-04-20 AUTH-3513: Checks header for app info in case response is a 403/401 from the edge
|
||||||
|
- 2021-04-29 TUN-4000: Release notes for cloudflared replica model
|
||||||
|
- 2021-04-09 TUN-2853: rename STDIN-CONTROL env var to STDIN_CONTROL
|
||||||
|
- 2021-04-09 TUN-4206: Better error message when user is only using one ingress rule
|
||||||
|
|
||||||
2021.4.0
|
2021.4.0
|
||||||
- 2021-04-05 TUN-4178: Fix component test for running as a service in MacOS to not assume a named tunnel
|
- 2021-04-05 TUN-4178: Fix component test for running as a service in MacOS to not assume a named tunnel
|
||||||
- 2021-04-05 TUN-4177: Running with proxy-dns should not prevent running Named Tunnels
|
- 2021-04-05 TUN-4177: Running with proxy-dns should not prevent running Named Tunnels
|
||||||
|
|
10
cfsetup.yaml
10
cfsetup.yaml
|
@ -1,4 +1,4 @@
|
||||||
pinned_go: &pinned_go go=1.15.7-1
|
pinned_go: &pinned_go go=1.16.4-1
|
||||||
pinned_go_fips: &pinned_go_fips go-fips=1.15.5-3
|
pinned_go_fips: &pinned_go_fips go-fips=1.15.5-3
|
||||||
|
|
||||||
build_dir: &build_dir /cfsetup_build
|
build_dir: &build_dir /cfsetup_build
|
||||||
|
@ -279,8 +279,8 @@ centos-7:
|
||||||
- https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
|
- https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
|
||||||
pre-cache:
|
pre-cache:
|
||||||
- yum install -y fakeroot
|
- yum install -y fakeroot
|
||||||
- wget https://golang.org/dl/go1.15.7.linux-amd64.tar.gz -P /tmp/
|
- wget https://golang.org/dl/go1.16.4.linux-amd64.tar.gz -P /tmp/
|
||||||
- tar -C /usr/local -xzf /tmp/go1.15.7.linux-amd64.tar.gz
|
- tar -C /usr/local -xzf /tmp/go1.16.4.linux-amd64.tar.gz
|
||||||
post-cache:
|
post-cache:
|
||||||
- export PATH=$PATH:/usr/local/go/bin
|
- export PATH=$PATH:/usr/local/go/bin
|
||||||
- export GOOS=linux
|
- export GOOS=linux
|
||||||
|
@ -291,8 +291,8 @@ centos-7:
|
||||||
builddeps: *el7_builddeps
|
builddeps: *el7_builddeps
|
||||||
pre-cache:
|
pre-cache:
|
||||||
- yum install -y fakeroot
|
- yum install -y fakeroot
|
||||||
- wget https://golang.org/dl/go1.15.7.linux-amd64.tar.gz -P /tmp/
|
- wget https://golang.org/dl/go1.16.4.linux-amd64.tar.gz -P /tmp/
|
||||||
- tar -C /usr/local -xzf /tmp/go1.15.7.linux-amd64.tar.gz
|
- tar -C /usr/local -xzf /tmp/go1.16.4.linux-amd64.tar.gz
|
||||||
post-cache:
|
post-cache:
|
||||||
- export PATH=$PATH:/usr/local/go/bin
|
- export PATH=$PATH:/usr/local/go/bin
|
||||||
- export GOOS=linux
|
- export GOOS=linux
|
||||||
|
|
|
@ -13,27 +13,38 @@ func Action(actionFunc cli.ActionFunc) cli.ActionFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConfiguredAction(actionFunc cli.ActionFunc) cli.ActionFunc {
|
func ConfiguredAction(actionFunc cli.ActionFunc) cli.ActionFunc {
|
||||||
|
// Adapt actionFunc to the type signature required by ConfiguredActionWithWarnings
|
||||||
|
f := func(context *cli.Context, _ string) error {
|
||||||
|
return actionFunc(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConfiguredActionWithWarnings(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just like ConfiguredAction, but accepts a second parameter with configuration warnings.
|
||||||
|
func ConfiguredActionWithWarnings(actionFunc func(*cli.Context, string) error) cli.ActionFunc {
|
||||||
return WithErrorHandler(func(c *cli.Context) error {
|
return WithErrorHandler(func(c *cli.Context) error {
|
||||||
if err := setFlagsFromConfigFile(c); err != nil {
|
warnings, err := setFlagsFromConfigFile(c)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return actionFunc(c)
|
return actionFunc(c, warnings)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFlagsFromConfigFile(c *cli.Context) error {
|
func setFlagsFromConfigFile(c *cli.Context) (configWarnings string, err error) {
|
||||||
const errorExitCode = 1
|
const errorExitCode = 1
|
||||||
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
|
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
|
||||||
inputSource, err := config.ReadConfigFile(c, log)
|
inputSource, warnings, err := config.ReadConfigFile(c, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == config.ErrNoConfigFile {
|
if err == config.ErrNoConfigFile {
|
||||||
return nil
|
return "", nil
|
||||||
}
|
}
|
||||||
return cli.Exit(err, errorExitCode)
|
return "", cli.Exit(err, errorExitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := altsrc.ApplyInputSource(c, inputSource); err != nil {
|
if err := altsrc.ApplyInputSource(c, inputSource); err != nil {
|
||||||
return cli.Exit(err, errorExitCode)
|
return "", cli.Exit(err, errorExitCode)
|
||||||
}
|
}
|
||||||
return nil
|
return warnings, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,7 +238,7 @@ func installLinuxService(c *cli.Context) error {
|
||||||
"--origincert", serviceConfigDir + "/" + serviceCredentialFile,
|
"--origincert", serviceConfigDir + "/" + serviceCredentialFile,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
src, err := config.ReadConfigFile(c, log)
|
src, _, err := config.ReadConfigFile(c, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
homedir "github.com/mitchellh/go-homedir"
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"go.uber.org/automaxprocs/maxprocs"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/access"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/access"
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||||
|
@ -47,6 +48,7 @@ func main() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
metrics.RegisterBuildInfo(BuildTime, Version)
|
metrics.RegisterBuildInfo(BuildTime, Version)
|
||||||
raven.SetRelease(Version)
|
raven.SetRelease(Version)
|
||||||
|
maxprocs.Set()
|
||||||
|
|
||||||
// Graceful shutdown channel used by the app. When closed, app must terminate gracefully.
|
// Graceful shutdown channel used by the app. When closed, app must terminate gracefully.
|
||||||
// Windows service manager closes this channel when it receives stop command.
|
// Windows service manager closes this channel when it receives stop command.
|
||||||
|
|
|
@ -181,7 +181,7 @@ func runAdhocNamedTunnel(sc *subcommandContext, name, credentialsOutputPath stri
|
||||||
|
|
||||||
if r, ok := routeFromFlag(sc.c); ok {
|
if r, ok := routeFromFlag(sc.c); ok {
|
||||||
if res, err := sc.route(tunnel.ID, r); err != nil {
|
if res, err := sc.route(tunnel.ID, r); err != nil {
|
||||||
sc.log.Err(err).Msg("failed to create route, please create it manually")
|
sc.log.Err(err).Str("route", r.String()).Msg("failed to provision routing, please create it manually via Cloudflare dashboard or UI; most likely you already have a conflicting record there")
|
||||||
} else {
|
} else {
|
||||||
sc.log.Info().Msg(res.SuccessSummary())
|
sc.log.Info().Msg(res.SuccessSummary())
|
||||||
}
|
}
|
||||||
|
@ -699,7 +699,7 @@ func configureProxyFlags(shouldHide bool) []cli.Flag {
|
||||||
Hidden: shouldHide,
|
Hidden: shouldHide,
|
||||||
}),
|
}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{
|
altsrc.NewDurationFlag(&cli.DurationFlag{
|
||||||
Name: ingress.ProxyTCPKeepAlive,
|
Name: ingress.ProxyTCPKeepAliveFlag,
|
||||||
Usage: "HTTP proxy TCP keepalive duration",
|
Usage: "HTTP proxy TCP keepalive duration",
|
||||||
Value: time.Second * 30,
|
Value: time.Second * 30,
|
||||||
Hidden: shouldHide,
|
Hidden: shouldHide,
|
||||||
|
|
|
@ -160,10 +160,6 @@ func prepareTunnelConfig(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Str(LogFieldHostname, configHostname).Msg("Invalid hostname")
|
log.Err(err).Str(LogFieldHostname, configHostname).Msg("Invalid hostname")
|
||||||
return nil, ingress.Ingress{}, errors.Wrap(err, "Invalid hostname")
|
return nil, ingress.Ingress{}, errors.Wrap(err, "Invalid hostname")
|
||||||
} else if hostname != "" && isNamedTunnel {
|
|
||||||
log.Warn().Msg("The property `hostname` in your configuration is ignored because you configured a Named Tunnel " +
|
|
||||||
"in the property `tunnel`. Make sure to provision the routing (e.g. via `cloudflared tunnel route`) or else " +
|
|
||||||
"your origin will not be reachable. You should remove the `hostname` property to avoid this warning.")
|
|
||||||
}
|
}
|
||||||
isFreeTunnel := hostname == ""
|
isFreeTunnel := hostname == ""
|
||||||
clientID := c.String("id")
|
clientID := c.String("id")
|
||||||
|
|
|
@ -45,7 +45,7 @@ func buildIngressSubcommand() *cli.Command {
|
||||||
func buildValidateIngressCommand() *cli.Command {
|
func buildValidateIngressCommand() *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "validate",
|
Name: "validate",
|
||||||
Action: cliutil.ConfiguredAction(validateIngressCommand),
|
Action: cliutil.ConfiguredActionWithWarnings(validateIngressCommand),
|
||||||
Usage: "Validate the ingress configuration ",
|
Usage: "Validate the ingress configuration ",
|
||||||
UsageText: "cloudflared tunnel [--config FILEPATH] ingress validate",
|
UsageText: "cloudflared tunnel [--config FILEPATH] ingress validate",
|
||||||
Description: "Validates the configuration file, ensuring your ingress rules are OK.",
|
Description: "Validates the configuration file, ensuring your ingress rules are OK.",
|
||||||
|
@ -68,7 +68,7 @@ func buildTestURLCommand() *cli.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateIngressCommand check the syntax of the ingress rules in the cloudflared config file
|
// validateIngressCommand check the syntax of the ingress rules in the cloudflared config file
|
||||||
func validateIngressCommand(c *cli.Context) error {
|
func validateIngressCommand(c *cli.Context, warnings string) error {
|
||||||
conf := config.GetConfiguration()
|
conf := config.GetConfiguration()
|
||||||
if conf.Source() == "" {
|
if conf.Source() == "" {
|
||||||
fmt.Println("No configuration file was found. Please create one, or use the --config flag to specify its filepath. You can use the help command to learn more about configuration files")
|
fmt.Println("No configuration file was found. Please create one, or use the --config flag to specify its filepath. You can use the help command to learn more about configuration files")
|
||||||
|
@ -81,6 +81,11 @@ func validateIngressCommand(c *cli.Context) error {
|
||||||
if c.IsSet("url") {
|
if c.IsSet("url") {
|
||||||
return ingress.ErrURLIncompatibleWithIngress
|
return ingress.ErrURLIncompatibleWithIngress
|
||||||
}
|
}
|
||||||
|
if warnings != "" {
|
||||||
|
fmt.Println("Warning: unused keys detected in your config file. Here is a list of unused keys:")
|
||||||
|
fmt.Println(warnings)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
fmt.Println("OK")
|
fmt.Println("OK")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package tunnel
|
package tunnel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -595,6 +595,12 @@ func runCommand(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.String("hostname") != "" {
|
||||||
|
sc.log.Warn().Msg("The property `hostname` in your configuration is ignored because you configured a Named Tunnel " +
|
||||||
|
"in the property `tunnel` to run. Make sure to provision the routing (e.g. via `cloudflared tunnel route dns/lb`) or else " +
|
||||||
|
"your origin will not be reachable. You should remove the `hostname` property to avoid this warning.")
|
||||||
|
}
|
||||||
|
|
||||||
return runNamedTunnel(sc, tunnelRef)
|
return runNamedTunnel(sc, tunnelRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package tunnel
|
package tunnel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -95,7 +94,7 @@ func TestTunnelfilePath(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
homeDir, err := homedir.Dir()
|
homeDir, err := homedir.Dir()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
expected := fmt.Sprintf("%s/.cloudflared/%v.json", homeDir, tunnelID)
|
expected := filepath.Join(homeDir, ".cloudflared", tunnelID.String()+".json")
|
||||||
assert.Equal(t, expected, actual)
|
assert.Equal(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -13,6 +15,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -21,6 +24,8 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var testFilePath = filepath.Join(os.TempDir(), "test")
|
||||||
|
|
||||||
func respondWithJSON(w http.ResponseWriter, v interface{}, status int) {
|
func respondWithJSON(w http.ResponseWriter, v interface{}, status int) {
|
||||||
data, _ := json.Marshal(v)
|
data, _ := json.Marshal(v)
|
||||||
|
|
||||||
|
@ -208,7 +213,6 @@ func TestUpdateService(t *testing.T) {
|
||||||
ts := createServer()
|
ts := createServer()
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
testFilePath := "tmpfile"
|
|
||||||
createTestFile(t, testFilePath)
|
createTestFile(t, testFilePath)
|
||||||
defer os.Remove(testFilePath)
|
defer os.Remove(testFilePath)
|
||||||
log.Println("server url: ", ts.URL)
|
log.Println("server url: ", ts.URL)
|
||||||
|
@ -229,7 +233,6 @@ func TestBetaUpdateService(t *testing.T) {
|
||||||
ts := createServer()
|
ts := createServer()
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
testFilePath := "tmpfile"
|
|
||||||
createTestFile(t, testFilePath)
|
createTestFile(t, testFilePath)
|
||||||
defer os.Remove(testFilePath)
|
defer os.Remove(testFilePath)
|
||||||
|
|
||||||
|
@ -249,7 +252,6 @@ func TestFailUpdateService(t *testing.T) {
|
||||||
ts := createServer()
|
ts := createServer()
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
testFilePath := "tmpfile"
|
|
||||||
createTestFile(t, testFilePath)
|
createTestFile(t, testFilePath)
|
||||||
defer os.Remove(testFilePath)
|
defer os.Remove(testFilePath)
|
||||||
|
|
||||||
|
@ -263,7 +265,6 @@ func TestNoUpdateService(t *testing.T) {
|
||||||
ts := createServer()
|
ts := createServer()
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
testFilePath := "tmpfile"
|
|
||||||
createTestFile(t, testFilePath)
|
createTestFile(t, testFilePath)
|
||||||
defer os.Remove(testFilePath)
|
defer os.Remove(testFilePath)
|
||||||
|
|
||||||
|
@ -278,7 +279,6 @@ func TestForcedUpdateService(t *testing.T) {
|
||||||
ts := createServer()
|
ts := createServer()
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
testFilePath := "tmpfile"
|
|
||||||
createTestFile(t, testFilePath)
|
createTestFile(t, testFilePath)
|
||||||
defer os.Remove(testFilePath)
|
defer os.Remove(testFilePath)
|
||||||
|
|
||||||
|
@ -298,7 +298,6 @@ func TestUpdateSpecificVersionService(t *testing.T) {
|
||||||
ts := createServer()
|
ts := createServer()
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
testFilePath := "tmpfile"
|
|
||||||
createTestFile(t, testFilePath)
|
createTestFile(t, testFilePath)
|
||||||
defer os.Remove(testFilePath)
|
defer os.Remove(testFilePath)
|
||||||
reqVersion := "2020.9.1"
|
reqVersion := "2020.9.1"
|
||||||
|
@ -319,7 +318,6 @@ func TestCompressedUpdateService(t *testing.T) {
|
||||||
ts := createServer()
|
ts := createServer()
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
testFilePath := "tmpfile"
|
|
||||||
createTestFile(t, testFilePath)
|
createTestFile(t, testFilePath)
|
||||||
defer os.Remove(testFilePath)
|
defer os.Remove(testFilePath)
|
||||||
|
|
||||||
|
@ -339,7 +337,6 @@ func TestUpdateWhenRunningKnownBuggyVersion(t *testing.T) {
|
||||||
ts := createServer()
|
ts := createServer()
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
testFilePath := "tmpfile"
|
|
||||||
createTestFile(t, testFilePath)
|
createTestFile(t, testFilePath)
|
||||||
defer os.Remove(testFilePath)
|
defer os.Remove(testFilePath)
|
||||||
|
|
||||||
|
|
|
@ -358,13 +358,13 @@ func GetConfiguration() *Configuration {
|
||||||
// ReadConfigFile returns InputSourceContext initialized from the configuration file.
|
// ReadConfigFile returns InputSourceContext initialized from the configuration file.
|
||||||
// On repeat calls returns with the same file, returns without reading the file again; however,
|
// On repeat calls returns with the same file, returns without reading the file again; however,
|
||||||
// if value of "config" flag changes, will read the new config file
|
// if value of "config" flag changes, will read the new config file
|
||||||
func ReadConfigFile(c *cli.Context, log *zerolog.Logger) (*configFileSettings, error) {
|
func ReadConfigFile(c *cli.Context, log *zerolog.Logger) (settings *configFileSettings, warnings string, err error) {
|
||||||
configFile := c.String("config")
|
configFile := c.String("config")
|
||||||
if configuration.Source() == configFile || configFile == "" {
|
if configuration.Source() == configFile || configFile == "" {
|
||||||
if configuration.Source() == "" {
|
if configuration.Source() == "" {
|
||||||
return nil, ErrNoConfigFile
|
return nil, "", ErrNoConfigFile
|
||||||
}
|
}
|
||||||
return &configuration, nil
|
return &configuration, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msgf("Loading configuration from %s", configFile)
|
log.Debug().Msgf("Loading configuration from %s", configFile)
|
||||||
|
@ -373,16 +373,27 @@ func ReadConfigFile(c *cli.Context, log *zerolog.Logger) (*configFileSettings, e
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
err = ErrNoConfigFile
|
err = ErrNoConfigFile
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
if err := yaml.NewDecoder(file).Decode(&configuration); err != nil {
|
if err := yaml.NewDecoder(file).Decode(&configuration); err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
log.Error().Msgf("Configuration file %s was empty", configFile)
|
log.Error().Msgf("Configuration file %s was empty", configFile)
|
||||||
return &configuration, nil
|
return &configuration, "", nil
|
||||||
}
|
}
|
||||||
return nil, errors.Wrap(err, "error parsing YAML in config file at "+configFile)
|
return nil, "", errors.Wrap(err, "error parsing YAML in config file at "+configFile)
|
||||||
}
|
}
|
||||||
configuration.sourceFile = configFile
|
configuration.sourceFile = configFile
|
||||||
return &configuration, nil
|
|
||||||
|
// Parse it again, with strict mode, to find warnings.
|
||||||
|
if file, err := os.Open(configFile); err == nil {
|
||||||
|
decoder := yaml.NewDecoder(file)
|
||||||
|
decoder.SetStrict(true)
|
||||||
|
var unusedConfig configFileSettings
|
||||||
|
if err := decoder.Decode(&unusedConfig); err != nil {
|
||||||
|
warnings = err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &configuration, warnings, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.15.7 as builder
|
FROM golang:1.16.4 as builder
|
||||||
ENV GO111MODULE=on \
|
ENV GO111MODULE=on \
|
||||||
CGO_ENABLED=0
|
CGO_ENABLED=0
|
||||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -43,6 +43,7 @@ require (
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/stretchr/testify v1.6.0
|
github.com/stretchr/testify v1.6.0
|
||||||
github.com/urfave/cli/v2 v2.2.0
|
github.com/urfave/cli/v2 v2.2.0
|
||||||
|
go.uber.org/automaxprocs v1.4.0
|
||||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
||||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73
|
golang.org/x/net v0.0.0-20200904194848-62affa334b73
|
||||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
|
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -609,6 +609,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
|
||||||
|
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
|
|
|
@ -402,7 +402,7 @@ func TestSingleOriginSetsConfig(t *testing.T) {
|
||||||
flagSet.Bool("hello-world", true, "")
|
flagSet.Bool("hello-world", true, "")
|
||||||
flagSet.Duration(ProxyConnectTimeoutFlag, time.Second, "")
|
flagSet.Duration(ProxyConnectTimeoutFlag, time.Second, "")
|
||||||
flagSet.Duration(ProxyTLSTimeoutFlag, time.Second, "")
|
flagSet.Duration(ProxyTLSTimeoutFlag, time.Second, "")
|
||||||
flagSet.Duration(ProxyTCPKeepAlive, time.Second, "")
|
flagSet.Duration(ProxyTCPKeepAliveFlag, time.Second, "")
|
||||||
flagSet.Bool(ProxyNoHappyEyeballsFlag, true, "")
|
flagSet.Bool(ProxyNoHappyEyeballsFlag, true, "")
|
||||||
flagSet.Int(ProxyKeepAliveConnectionsFlag, 10, "")
|
flagSet.Int(ProxyKeepAliveConnectionsFlag, 10, "")
|
||||||
flagSet.Duration(ProxyKeepAliveTimeoutFlag, time.Second, "")
|
flagSet.Duration(ProxyKeepAliveTimeoutFlag, time.Second, "")
|
||||||
|
@ -423,7 +423,7 @@ func TestSingleOriginSetsConfig(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = cliCtx.Set(ProxyTLSTimeoutFlag, "1s")
|
err = cliCtx.Set(ProxyTLSTimeoutFlag, "1s")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = cliCtx.Set(ProxyTCPKeepAlive, "1s")
|
err = cliCtx.Set(ProxyTCPKeepAliveFlag, "1s")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = cliCtx.Set(ProxyNoHappyEyeballsFlag, "true")
|
err = cliCtx.Set(ProxyNoHappyEyeballsFlag, "true")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -22,7 +22,7 @@ const (
|
||||||
Socks5Flag = "socks5"
|
Socks5Flag = "socks5"
|
||||||
ProxyConnectTimeoutFlag = "proxy-connect-timeout"
|
ProxyConnectTimeoutFlag = "proxy-connect-timeout"
|
||||||
ProxyTLSTimeoutFlag = "proxy-tls-timeout"
|
ProxyTLSTimeoutFlag = "proxy-tls-timeout"
|
||||||
ProxyTCPKeepAlive = "proxy-tcp-keepalive"
|
ProxyTCPKeepAliveFlag = "proxy-tcp-keepalive"
|
||||||
ProxyNoHappyEyeballsFlag = "proxy-no-happy-eyeballs"
|
ProxyNoHappyEyeballsFlag = "proxy-no-happy-eyeballs"
|
||||||
ProxyKeepAliveConnectionsFlag = "proxy-keepalive-connections"
|
ProxyKeepAliveConnectionsFlag = "proxy-keepalive-connections"
|
||||||
ProxyKeepAliveTimeoutFlag = "proxy-keepalive-timeout"
|
ProxyKeepAliveTimeoutFlag = "proxy-keepalive-timeout"
|
||||||
|
@ -60,7 +60,7 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
|
||||||
if flag := ProxyTLSTimeoutFlag; c.IsSet(flag) {
|
if flag := ProxyTLSTimeoutFlag; c.IsSet(flag) {
|
||||||
tlsTimeout = c.Duration(flag)
|
tlsTimeout = c.Duration(flag)
|
||||||
}
|
}
|
||||||
if flag := ProxyTCPKeepAlive; c.IsSet(flag) {
|
if flag := ProxyTCPKeepAliveFlag; c.IsSet(flag) {
|
||||||
tcpKeepAlive = c.Duration(flag)
|
tcpKeepAlive = c.Duration(flag)
|
||||||
}
|
}
|
||||||
if flag := ProxyNoHappyEyeballsFlag; c.IsSet(flag) {
|
if flag := ProxyNoHappyEyeballsFlag; c.IsSet(flag) {
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
package metrics
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Timer assumes the metrics is partitioned by one label
|
|
||||||
type Timer struct {
|
|
||||||
startTime map[string]time.Time
|
|
||||||
metrics *prometheus.HistogramVec
|
|
||||||
measureUnit time.Duration
|
|
||||||
labelKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTimer(metrics *prometheus.HistogramVec, unit time.Duration, labelKey string) *Timer {
|
|
||||||
return &Timer{
|
|
||||||
startTime: make(map[string]time.Time),
|
|
||||||
measureUnit: unit,
|
|
||||||
metrics: metrics,
|
|
||||||
labelKey: labelKey,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Timer) Start(labelVal string) {
|
|
||||||
i.startTime[labelVal] = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Timer) End(labelVal string) time.Duration {
|
|
||||||
if start, ok := i.startTime[labelVal]; ok {
|
|
||||||
return Latency(start, time.Now())
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Timer) Observe(measurement time.Duration, labelVal string) {
|
|
||||||
metricsLabels := prometheus.Labels{i.labelKey: labelVal}
|
|
||||||
i.metrics.With(metricsLabels).Observe(float64(measurement / i.measureUnit))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Timer) EndAndObserve(labelVal string) {
|
|
||||||
i.Observe(i.End(labelVal), labelVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Latency(startTime, endTime time.Time) time.Duration {
|
|
||||||
return endTime.Sub(startTime)
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package metrics
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEnd(t *testing.T) {
|
|
||||||
m := prometheus.NewHistogramVec(
|
|
||||||
prometheus.HistogramOpts{
|
|
||||||
Namespace: "TestCallLatencyWithoutMeasurement",
|
|
||||||
Name: "Latency",
|
|
||||||
Buckets: prometheus.LinearBuckets(0, 50, 100),
|
|
||||||
},
|
|
||||||
[]string{"key"},
|
|
||||||
)
|
|
||||||
timer := NewTimer(m, time.Millisecond, "key")
|
|
||||||
assert.Equal(t, time.Duration(0), timer.End("dne"))
|
|
||||||
timer.Start("test")
|
|
||||||
assert.NotEqual(t, time.Duration(0), timer.End("test"))
|
|
||||||
}
|
|
|
@ -238,8 +238,13 @@ func (p *proxy) logRequest(r *http.Request, fields logFields) {
|
||||||
} else {
|
} else {
|
||||||
p.log.Debug().Msgf("All requests should have a CF-RAY header. Please open a support ticket with Cloudflare. %s %s %s ", r.Method, r.URL, r.Proto)
|
p.log.Debug().Msgf("All requests should have a CF-RAY header. Please open a support ticket with Cloudflare. %s %s %s ", r.Method, r.URL, r.Proto)
|
||||||
}
|
}
|
||||||
p.log.Debug().Msgf("CF-RAY: %s Request Headers %+v", fields.cfRay, r.Header)
|
p.log.Debug().
|
||||||
p.log.Debug().Msgf("CF-RAY: %s Serving with ingress rule %v", fields.cfRay, fields.rule)
|
Str("CF-RAY", fields.cfRay).
|
||||||
|
Str("Header", fmt.Sprintf("%+v", r.Header)).
|
||||||
|
Str("host", r.Host).
|
||||||
|
Str("path", r.URL.Path).
|
||||||
|
Interface("rule", fields.rule).
|
||||||
|
Msg("Inbound request")
|
||||||
|
|
||||||
if contentLen := r.ContentLength; contentLen == -1 {
|
if contentLen := r.ContentLength; contentLen == -1 {
|
||||||
p.log.Debug().Msgf("CF-RAY: %s Request Content length unknown", fields.cfRay)
|
p.log.Debug().Msgf("CF-RAY: %s Request Content length unknown", fields.cfRay)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package sshgen
|
package sshgen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -46,7 +46,7 @@ type signalHandler struct {
|
||||||
signals []os.Signal
|
signals []os.Signal
|
||||||
}
|
}
|
||||||
|
|
||||||
type appJWTPayload struct {
|
type jwtPayload struct {
|
||||||
Aud []string `json:"aud"`
|
Aud []string `json:"aud"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Exp int `json:"exp"`
|
Exp int `json:"exp"`
|
||||||
|
@ -57,17 +57,12 @@ type appJWTPayload struct {
|
||||||
Subt string `json:"sub"`
|
Subt string `json:"sub"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type orgJWTPayload struct {
|
|
||||||
appJWTPayload
|
|
||||||
Aud string `json:"aud"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type transferServiceResponse struct {
|
type transferServiceResponse struct {
|
||||||
AppToken string `json:"app_token"`
|
AppToken string `json:"app_token"`
|
||||||
OrgToken string `json:"org_token"`
|
OrgToken string `json:"org_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p appJWTPayload) isExpired() bool {
|
func (p jwtPayload) isExpired() bool {
|
||||||
return int(time.Now().Unix()) > p.Exp
|
return int(time.Now().Unix()) > p.Exp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,7 +341,7 @@ func GetOrgTokenIfExists(authDomain string) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
var payload orgJWTPayload
|
var payload jwtPayload
|
||||||
err = json.Unmarshal(token.Payload, &payload)
|
err = json.Unmarshal(token.Payload, &payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -368,7 +363,7 @@ func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
var payload appJWTPayload
|
var payload jwtPayload
|
||||||
err = json.Unmarshal(token.Payload, &payload)
|
err = json.Unmarshal(token.Payload, &payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -72,6 +72,7 @@ type Route interface {
|
||||||
json.Marshaler
|
json.Marshaler
|
||||||
RecordType() string
|
RecordType() string
|
||||||
UnmarshalResult(body io.Reader) (RouteResult, error)
|
UnmarshalResult(body io.Reader) (RouteResult, error)
|
||||||
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteResult interface {
|
type RouteResult interface {
|
||||||
|
@ -116,6 +117,10 @@ func (dr *DNSRoute) RecordType() string {
|
||||||
return "dns"
|
return "dns"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dr *DNSRoute) String() string {
|
||||||
|
return fmt.Sprintf("%s %s", dr.RecordType(), dr.userHostname)
|
||||||
|
}
|
||||||
|
|
||||||
func (res *DNSRouteResult) SuccessSummary() string {
|
func (res *DNSRouteResult) SuccessSummary() string {
|
||||||
var msgFmt string
|
var msgFmt string
|
||||||
switch res.CName {
|
switch res.CName {
|
||||||
|
@ -164,6 +169,10 @@ func (lr *LBRoute) RecordType() string {
|
||||||
return "lb"
|
return "lb"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (lb *LBRoute) String() string {
|
||||||
|
return fmt.Sprintf("%s %s %s", lb.RecordType(), lb.lbName, lb.lbPool)
|
||||||
|
}
|
||||||
|
|
||||||
func (lr *LBRoute) UnmarshalResult(body io.Reader) (RouteResult, error) {
|
func (lr *LBRoute) UnmarshalResult(body io.Reader) (RouteResult, error) {
|
||||||
var result LBRouteResult
|
var result LBRouteResult
|
||||||
err := parseResponse(body, &result)
|
err := parseResponse(body, &result)
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package cgroups
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CGroup represents the data structure for a Linux control group.
|
||||||
|
type CGroup struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCGroup returns a new *CGroup from a given path.
|
||||||
|
func NewCGroup(path string) *CGroup {
|
||||||
|
return &CGroup{path: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the path of the CGroup*.
|
||||||
|
func (cg *CGroup) Path() string {
|
||||||
|
return cg.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamPath returns the path of the given cgroup param under itself.
|
||||||
|
func (cg *CGroup) ParamPath(param string) string {
|
||||||
|
return filepath.Join(cg.path, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readFirstLine reads the first line from a cgroup param file.
|
||||||
|
func (cg *CGroup) readFirstLine(param string) (string, error) {
|
||||||
|
paramFile, err := os.Open(cg.ParamPath(param))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer paramFile.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(paramFile)
|
||||||
|
if scanner.Scan() {
|
||||||
|
return scanner.Text(), nil
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "", io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// readInt parses the first line from a cgroup param file as int.
|
||||||
|
func (cg *CGroup) readInt(param string) (int, error) {
|
||||||
|
text, err := cg.readFirstLine(param)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return strconv.Atoi(text)
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package cgroups
|
||||||
|
|
||||||
|
const (
|
||||||
|
// _cgroupFSType is the Linux CGroup file system type used in
|
||||||
|
// `/proc/$PID/mountinfo`.
|
||||||
|
_cgroupFSType = "cgroup"
|
||||||
|
// _cgroupSubsysCPU is the CPU CGroup subsystem.
|
||||||
|
_cgroupSubsysCPU = "cpu"
|
||||||
|
// _cgroupSubsysCPUAcct is the CPU accounting CGroup subsystem.
|
||||||
|
_cgroupSubsysCPUAcct = "cpuacct"
|
||||||
|
// _cgroupSubsysCPUSet is the CPUSet CGroup subsystem.
|
||||||
|
_cgroupSubsysCPUSet = "cpuset"
|
||||||
|
// _cgroupSubsysMemory is the Memory CGroup subsystem.
|
||||||
|
_cgroupSubsysMemory = "memory"
|
||||||
|
|
||||||
|
// _cgroupCPUCFSQuotaUsParam is the file name for the CGroup CFS quota
|
||||||
|
// parameter.
|
||||||
|
_cgroupCPUCFSQuotaUsParam = "cpu.cfs_quota_us"
|
||||||
|
// _cgroupCPUCFSPeriodUsParam is the file name for the CGroup CFS period
|
||||||
|
// parameter.
|
||||||
|
_cgroupCPUCFSPeriodUsParam = "cpu.cfs_period_us"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_procPathCGroup = "/proc/self/cgroup"
|
||||||
|
_procPathMountInfo = "/proc/self/mountinfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CGroups is a map that associates each CGroup with its subsystem name.
|
||||||
|
type CGroups map[string]*CGroup
|
||||||
|
|
||||||
|
// NewCGroups returns a new *CGroups from given `mountinfo` and `cgroup` files
|
||||||
|
// under for some process under `/proc` file system (see also proc(5) for more
|
||||||
|
// information).
|
||||||
|
func NewCGroups(procPathMountInfo, procPathCGroup string) (CGroups, error) {
|
||||||
|
cgroupSubsystems, err := parseCGroupSubsystems(procPathCGroup)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cgroups := make(CGroups)
|
||||||
|
newMountPoint := func(mp *MountPoint) error {
|
||||||
|
if mp.FSType != _cgroupFSType {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range mp.SuperOptions {
|
||||||
|
subsys, exists := cgroupSubsystems[opt]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cgroupPath, err := mp.Translate(subsys.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cgroups[opt] = NewCGroup(cgroupPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cgroups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCGroupsForCurrentProcess returns a new *CGroups instance for the current
|
||||||
|
// process.
|
||||||
|
func NewCGroupsForCurrentProcess() (CGroups, error) {
|
||||||
|
return NewCGroups(_procPathMountInfo, _procPathCGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPUQuota returns the CPU quota applied with the CPU cgroup controller.
|
||||||
|
// It is a result of `cpu.cfs_quota_us / cpu.cfs_period_us`. If the value of
|
||||||
|
// `cpu.cfs_quota_us` was not set (-1), the method returns `(-1, nil)`.
|
||||||
|
func (cg CGroups) CPUQuota() (float64, bool, error) {
|
||||||
|
cpuCGroup, exists := cg[_cgroupSubsysCPU]
|
||||||
|
if !exists {
|
||||||
|
return -1, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfsQuotaUs, err := cpuCGroup.readInt(_cgroupCPUCFSQuotaUsParam)
|
||||||
|
if defined := cfsQuotaUs > 0; err != nil || !defined {
|
||||||
|
return -1, defined, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfsPeriodUs, err := cpuCGroup.readInt(_cgroupCPUCFSPeriodUsParam)
|
||||||
|
if err != nil {
|
||||||
|
return -1, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return float64(cfsQuotaUs) / float64(cfsPeriodUs), true, nil
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package cgroups provides utilities to access Linux control group (CGroups)
|
||||||
|
// parameters (CPU quota, for example) for a given process.
|
||||||
|
package cgroups
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package cgroups
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type cgroupSubsysFormatInvalidError struct {
|
||||||
|
line string
|
||||||
|
}
|
||||||
|
|
||||||
|
type mountPointFormatInvalidError struct {
|
||||||
|
line string
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathNotExposedFromMountPointError struct {
|
||||||
|
mountPoint string
|
||||||
|
root string
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err cgroupSubsysFormatInvalidError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid format for CGroupSubsys: %q", err.line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err mountPointFormatInvalidError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid format for MountPoint: %q", err.line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err pathNotExposedFromMountPointError) Error() string {
|
||||||
|
return fmt.Sprintf("path %q is not a descendant of mount point root %q and cannot be exposed from %q", err.path, err.root, err.mountPoint)
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package cgroups
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_mountInfoSep = " "
|
||||||
|
_mountInfoOptsSep = ","
|
||||||
|
_mountInfoOptionalFieldsSep = "-"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_miFieldIDMountID = iota
|
||||||
|
_miFieldIDParentID
|
||||||
|
_miFieldIDDeviceID
|
||||||
|
_miFieldIDRoot
|
||||||
|
_miFieldIDMountPoint
|
||||||
|
_miFieldIDOptions
|
||||||
|
_miFieldIDOptionalFields
|
||||||
|
|
||||||
|
_miFieldCountFirstHalf
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_miFieldOffsetFSType = iota
|
||||||
|
_miFieldOffsetMountSource
|
||||||
|
_miFieldOffsetSuperOptions
|
||||||
|
|
||||||
|
_miFieldCountSecondHalf
|
||||||
|
)
|
||||||
|
|
||||||
|
const _miFieldCountMin = _miFieldCountFirstHalf + _miFieldCountSecondHalf
|
||||||
|
|
||||||
|
// MountPoint is the data structure for the mount points in
|
||||||
|
// `/proc/$PID/mountinfo`. See also proc(5) for more information.
|
||||||
|
type MountPoint struct {
|
||||||
|
MountID int
|
||||||
|
ParentID int
|
||||||
|
DeviceID string
|
||||||
|
Root string
|
||||||
|
MountPoint string
|
||||||
|
Options []string
|
||||||
|
OptionalFields []string
|
||||||
|
FSType string
|
||||||
|
MountSource string
|
||||||
|
SuperOptions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMountPointFromLine parses a line read from `/proc/$PID/mountinfo` and
|
||||||
|
// returns a new *MountPoint.
|
||||||
|
func NewMountPointFromLine(line string) (*MountPoint, error) {
|
||||||
|
fields := strings.Split(line, _mountInfoSep)
|
||||||
|
|
||||||
|
if len(fields) < _miFieldCountMin {
|
||||||
|
return nil, mountPointFormatInvalidError{line}
|
||||||
|
}
|
||||||
|
|
||||||
|
mountID, err := strconv.Atoi(fields[_miFieldIDMountID])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parentID, err := strconv.Atoi(fields[_miFieldIDParentID])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, field := range fields[_miFieldIDOptionalFields:] {
|
||||||
|
if field == _mountInfoOptionalFieldsSep {
|
||||||
|
fsTypeStart := _miFieldIDOptionalFields + i + 1
|
||||||
|
|
||||||
|
if len(fields) != fsTypeStart+_miFieldCountSecondHalf {
|
||||||
|
return nil, mountPointFormatInvalidError{line}
|
||||||
|
}
|
||||||
|
|
||||||
|
miFieldIDFSType := _miFieldOffsetFSType + fsTypeStart
|
||||||
|
miFieldIDMountSource := _miFieldOffsetMountSource + fsTypeStart
|
||||||
|
miFieldIDSuperOptions := _miFieldOffsetSuperOptions + fsTypeStart
|
||||||
|
|
||||||
|
return &MountPoint{
|
||||||
|
MountID: mountID,
|
||||||
|
ParentID: parentID,
|
||||||
|
DeviceID: fields[_miFieldIDDeviceID],
|
||||||
|
Root: fields[_miFieldIDRoot],
|
||||||
|
MountPoint: fields[_miFieldIDMountPoint],
|
||||||
|
Options: strings.Split(fields[_miFieldIDOptions], _mountInfoOptsSep),
|
||||||
|
OptionalFields: fields[_miFieldIDOptionalFields:(fsTypeStart - 1)],
|
||||||
|
FSType: fields[miFieldIDFSType],
|
||||||
|
MountSource: fields[miFieldIDMountSource],
|
||||||
|
SuperOptions: strings.Split(fields[miFieldIDSuperOptions], _mountInfoOptsSep),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, mountPointFormatInvalidError{line}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate converts an absolute path inside the *MountPoint's file system to
|
||||||
|
// the host file system path in the mount namespace the *MountPoint belongs to.
|
||||||
|
func (mp *MountPoint) Translate(absPath string) (string, error) {
|
||||||
|
relPath, err := filepath.Rel(mp.Root, absPath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if relPath == ".." || strings.HasPrefix(relPath, "../") {
|
||||||
|
return "", pathNotExposedFromMountPointError{
|
||||||
|
mountPoint: mp.MountPoint,
|
||||||
|
root: mp.Root,
|
||||||
|
path: absPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(mp.MountPoint, relPath), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMountInfo parses procPathMountInfo (usually at `/proc/$PID/mountinfo`)
|
||||||
|
// and yields parsed *MountPoint into newMountPoint.
|
||||||
|
func parseMountInfo(procPathMountInfo string, newMountPoint func(*MountPoint) error) error {
|
||||||
|
mountInfoFile, err := os.Open(procPathMountInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer mountInfoFile.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(mountInfoFile)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
mountPoint, err := NewMountPointFromLine(scanner.Text())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := newMountPoint(mountPoint); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanner.Err()
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package cgroups
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_cgroupSep = ":"
|
||||||
|
_cgroupSubsysSep = ","
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_csFieldIDID = iota
|
||||||
|
_csFieldIDSubsystems
|
||||||
|
_csFieldIDName
|
||||||
|
_csFieldCount
|
||||||
|
)
|
||||||
|
|
||||||
|
// CGroupSubsys represents the data structure for entities in
|
||||||
|
// `/proc/$PID/cgroup`. See also proc(5) for more information.
|
||||||
|
type CGroupSubsys struct {
|
||||||
|
ID int
|
||||||
|
Subsystems []string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCGroupSubsysFromLine returns a new *CGroupSubsys by parsing a string in
|
||||||
|
// the format of `/proc/$PID/cgroup`
|
||||||
|
func NewCGroupSubsysFromLine(line string) (*CGroupSubsys, error) {
|
||||||
|
fields := strings.SplitN(line, _cgroupSep, _csFieldCount)
|
||||||
|
|
||||||
|
if len(fields) != _csFieldCount {
|
||||||
|
return nil, cgroupSubsysFormatInvalidError{line}
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(fields[_csFieldIDID])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cgroup := &CGroupSubsys{
|
||||||
|
ID: id,
|
||||||
|
Subsystems: strings.Split(fields[_csFieldIDSubsystems], _cgroupSubsysSep),
|
||||||
|
Name: fields[_csFieldIDName],
|
||||||
|
}
|
||||||
|
|
||||||
|
return cgroup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCGroupSubsystems parses procPathCGroup (usually at `/proc/$PID/cgroup`)
|
||||||
|
// and returns a new map[string]*CGroupSubsys.
|
||||||
|
func parseCGroupSubsystems(procPathCGroup string) (map[string]*CGroupSubsys, error) {
|
||||||
|
cgroupFile, err := os.Open(procPathCGroup)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cgroupFile.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(cgroupFile)
|
||||||
|
subsystems := make(map[string]*CGroupSubsys)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
cgroup, err := NewCGroupSubsysFromLine(scanner.Text())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, subsys := range cgroup.Subsystems {
|
||||||
|
subsystems[subsys] = cgroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return subsystems, nil
|
||||||
|
}
|
49
vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go
generated
vendored
Normal file
49
vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
cg "go.uber.org/automaxprocs/internal/cgroups"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process
|
||||||
|
// to a valid GOMAXPROCS value.
|
||||||
|
func CPUQuotaToGOMAXPROCS(minValue int) (int, CPUQuotaStatus, error) {
|
||||||
|
cgroups, err := cg.NewCGroupsForCurrentProcess()
|
||||||
|
if err != nil {
|
||||||
|
return -1, CPUQuotaUndefined, err
|
||||||
|
}
|
||||||
|
|
||||||
|
quota, defined, err := cgroups.CPUQuota()
|
||||||
|
if !defined || err != nil {
|
||||||
|
return -1, CPUQuotaUndefined, err
|
||||||
|
}
|
||||||
|
|
||||||
|
maxProcs := int(math.Floor(quota))
|
||||||
|
if minValue > 0 && maxProcs < minValue {
|
||||||
|
return minValue, CPUQuotaMinUsed, nil
|
||||||
|
}
|
||||||
|
return maxProcs, CPUQuotaUsed, nil
|
||||||
|
}
|
30
vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go
generated
vendored
Normal file
30
vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
// CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process
|
||||||
|
// to a valid GOMAXPROCS value. This is Linux-specific and not supported in the
|
||||||
|
// current OS.
|
||||||
|
func CPUQuotaToGOMAXPROCS(_ int) (int, CPUQuotaStatus, error) {
|
||||||
|
return -1, CPUQuotaUndefined, nil
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
// CPUQuotaStatus presents the status of how CPU quota is used
|
||||||
|
type CPUQuotaStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CPUQuotaUndefined is returned when CPU quota is undefined
|
||||||
|
CPUQuotaUndefined CPUQuotaStatus = iota
|
||||||
|
// CPUQuotaUsed is returned when a valid CPU quota can be used
|
||||||
|
CPUQuotaUsed
|
||||||
|
// CPUQuotaMinUsed is return when CPU quota is smaller than the min value
|
||||||
|
CPUQuotaMinUsed
|
||||||
|
)
|
|
@ -0,0 +1,130 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package maxprocs lets Go programs easily configure runtime.GOMAXPROCS to
|
||||||
|
// match the configured Linux CPU quota. Unlike the top-level automaxprocs
|
||||||
|
// package, it lets the caller configure logging and handle errors.
|
||||||
|
package maxprocs // import "go.uber.org/automaxprocs/maxprocs"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
iruntime "go.uber.org/automaxprocs/internal/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
const _maxProcsKey = "GOMAXPROCS"
|
||||||
|
|
||||||
|
func currentMaxProcs() int {
|
||||||
|
return runtime.GOMAXPROCS(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
printf func(string, ...interface{})
|
||||||
|
procs func(int) (int, iruntime.CPUQuotaStatus, error)
|
||||||
|
minGOMAXPROCS int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *config) log(fmt string, args ...interface{}) {
|
||||||
|
if c.printf != nil {
|
||||||
|
c.printf(fmt, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Option alters the behavior of Set.
|
||||||
|
type Option interface {
|
||||||
|
apply(*config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger uses the supplied printf implementation for log output. By default,
|
||||||
|
// Set doesn't log anything.
|
||||||
|
func Logger(printf func(string, ...interface{})) Option {
|
||||||
|
return optionFunc(func(cfg *config) {
|
||||||
|
cfg.printf = printf
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Min sets the minimum GOMAXPROCS value that will be used.
|
||||||
|
// Any value below 1 is ignored.
|
||||||
|
func Min(n int) Option {
|
||||||
|
return optionFunc(func(cfg *config) {
|
||||||
|
if n >= 1 {
|
||||||
|
cfg.minGOMAXPROCS = n
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionFunc func(*config)
|
||||||
|
|
||||||
|
func (of optionFunc) apply(cfg *config) { of(cfg) }
|
||||||
|
|
||||||
|
// Set GOMAXPROCS to match the Linux container CPU quota (if any), returning
|
||||||
|
// any error encountered and an undo function.
|
||||||
|
//
|
||||||
|
// Set is a no-op on non-Linux systems and in Linux environments without a
|
||||||
|
// configured CPU quota.
|
||||||
|
func Set(opts ...Option) (func(), error) {
|
||||||
|
cfg := &config{
|
||||||
|
procs: iruntime.CPUQuotaToGOMAXPROCS,
|
||||||
|
minGOMAXPROCS: 1,
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
o.apply(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
undoNoop := func() {
|
||||||
|
cfg.log("maxprocs: No GOMAXPROCS change to reset")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Honor the GOMAXPROCS environment variable if present. Otherwise, amend
|
||||||
|
// `runtime.GOMAXPROCS()` with the current process' CPU quota if the OS is
|
||||||
|
// Linux, and guarantee a minimum value of 1. The minimum guaranteed value
|
||||||
|
// can be overriden using `maxprocs.Min()`.
|
||||||
|
if max, exists := os.LookupEnv(_maxProcsKey); exists {
|
||||||
|
cfg.log("maxprocs: Honoring GOMAXPROCS=%q as set in environment", max)
|
||||||
|
return undoNoop, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
maxProcs, status, err := cfg.procs(cfg.minGOMAXPROCS)
|
||||||
|
if err != nil {
|
||||||
|
return undoNoop, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == iruntime.CPUQuotaUndefined {
|
||||||
|
cfg.log("maxprocs: Leaving GOMAXPROCS=%v: CPU quota undefined", currentMaxProcs())
|
||||||
|
return undoNoop, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
prev := currentMaxProcs()
|
||||||
|
undo := func() {
|
||||||
|
cfg.log("maxprocs: Resetting GOMAXPROCS to %v", prev)
|
||||||
|
runtime.GOMAXPROCS(prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
case iruntime.CPUQuotaMinUsed:
|
||||||
|
cfg.log("maxprocs: Updating GOMAXPROCS=%v: using minimum allowed GOMAXPROCS", maxProcs)
|
||||||
|
case iruntime.CPUQuotaUsed:
|
||||||
|
cfg.log("maxprocs: Updating GOMAXPROCS=%v: determined from CPU quota", maxProcs)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.GOMAXPROCS(maxProcs)
|
||||||
|
return undo, nil
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package maxprocs
|
||||||
|
|
||||||
|
// Version is the current package version.
|
||||||
|
const Version = "1.4.0"
|
|
@ -250,6 +250,11 @@ github.com/stretchr/testify/require
|
||||||
## explicit
|
## explicit
|
||||||
github.com/urfave/cli/v2
|
github.com/urfave/cli/v2
|
||||||
github.com/urfave/cli/v2/altsrc
|
github.com/urfave/cli/v2/altsrc
|
||||||
|
# go.uber.org/automaxprocs v1.4.0
|
||||||
|
## explicit
|
||||||
|
go.uber.org/automaxprocs/internal/cgroups
|
||||||
|
go.uber.org/automaxprocs/internal/runtime
|
||||||
|
go.uber.org/automaxprocs/maxprocs
|
||||||
# golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
# golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
||||||
## explicit
|
## explicit
|
||||||
golang.org/x/crypto/blake2b
|
golang.org/x/crypto/blake2b
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package watcher
|
package watcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
Loading…
Reference in New Issue