mirror of
https://github.com/cloudflare/cloudflared
synced 2026-06-08 13:33:07 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d14f3b39a7 | |||
| fd0529748a | |||
| 9d3a7bd08e | |||
| 1cf6ae37eb | |||
| 66da530ba3 | |||
| e9167f7f58 | |||
| 4494a27fab | |||
| 63833b07dd | |||
| da4d0b2bae | |||
| 027168c23a | |||
| 50435546c5 | |||
| 9018ee5d5e | |||
| 12447677cf | |||
| 6106737281 | |||
| 8250b67a9f |
+25
@@ -1,5 +1,30 @@
|
||||
**Experimental**: This is a new format for release notes. The format and availability is subject to change.
|
||||
|
||||
## 2021.3.5
|
||||
|
||||
### Improvements
|
||||
|
||||
- HTTP/2 transport is now always chosen if origin server supports it and the service url scheme is HTTPS.
|
||||
This was previously done in a best attempt manner.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- The MacOS binaries were not successfully released in 2021.3.3 and 2021.3.4. This release is aimed at addressing that.
|
||||
|
||||
## 2021.3.3
|
||||
|
||||
### Improvements
|
||||
|
||||
- Tunnel create command, as well as, running ad-hoc tunnels using `cloudflared tunnel -name NAME`, will not overwrite
|
||||
existing files when writing tunnel credentials.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Tunnel create and delete commands no longer use path to credentials from the configuration file.
|
||||
If you need ot place tunnel credentials file at a specific location, you must use `--credentials-file` flag.
|
||||
- Access ssh-gen creates properly named keys for SSH short lived certs.
|
||||
|
||||
|
||||
## 2021.3.2
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -254,3 +254,7 @@ vet:
|
||||
.PHONY: msi
|
||||
msi: cloudflared
|
||||
go-msi make --msi cloudflared.msi --version $(MSI_VERSION)
|
||||
|
||||
.PHONY: goimports
|
||||
goimports:
|
||||
for d in $$(go list -mod=readonly -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc) ; do goimports -format-only -local github.com/cloudflare/cloudflared -w $$d ; done
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
2021.3.5
|
||||
- 2021-03-26 TUN-3896: http-service and tunnelstore client use http2 transport.
|
||||
- 2021-03-25 TUN-4125: Change component tests to run in CI with its own dedicated resources
|
||||
- 2021-03-26 Publish change log for 2021.3.5
|
||||
|
||||
2021.3.4
|
||||
|
||||
|
||||
2021.3.3
|
||||
- 2021-03-23 TUN-4111: Warn the user if both properties "tunnel" and "hostname" are used
|
||||
- 2021-03-23 TUN-4082: Test logging when running as a service
|
||||
- 2021-03-23 TUN-4112: Skip testing graceful shutdown with SIGINT on Windows
|
||||
- 2021-03-23 TUN-4116: Ingore credentials-file setting in configuration file during tunnel create and delete opeations.
|
||||
- 2021-03-23 TUN-4118: Don't overwrite existing file with tunnel credentials. For ad-hoc tunnels, this means tunnel won't start if there's a file in the way.
|
||||
- 2021-03-24 TUN-4123: Don't capture output in reconnect componet test
|
||||
- 2021-03-23 TUN-4067: Reformat code for consistent import order, grouping, and fix formatting. Added goimports target to the Makefile to make this easier in the future.
|
||||
- 2021-03-24 AUTH-3455: Generate short-lived ssh cert per hostname
|
||||
- 2021-03-25 Update changelog 2021.3.3
|
||||
|
||||
2021.3.2
|
||||
- 2021-03-23 TUN-4042: Capture cloudflared output to debug component tests
|
||||
- 2021-03-23 Publish changelog for 2021.3.2
|
||||
|
||||
+3
-3
@@ -11,11 +11,11 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/token"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/token"
|
||||
)
|
||||
|
||||
const LogFieldOriginURL = "originURL"
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
"github.com/cloudflare/cloudflared/socks"
|
||||
"github.com/cloudflare/cloudflared/token"
|
||||
cfwebsocket "github.com/cloudflare/cloudflared/websocket"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// Websocket is used to carry data via WS binary frames over the tunnel from client to the origin
|
||||
|
||||
+8
-4
@@ -195,13 +195,15 @@ stretch: &stretch
|
||||
- *pinned_go_fips
|
||||
- build-essential
|
||||
- gotest-to-teamcity
|
||||
pre-cache:
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
- go get github.com/BurntSushi/go-sumtype
|
||||
post-cache:
|
||||
- export GOOS=linux
|
||||
- export GOARCH=amd64
|
||||
- export FIPS=true
|
||||
# cd to a non-module directory: https://github.com/golang/go/issues/24250
|
||||
- (cd / && go get github.com/BurntSushi/go-sumtype)
|
||||
- export PATH="$HOME/go/bin:$PATH"
|
||||
- ./fmt-check.sh
|
||||
- make test | gotest-to-teamcity
|
||||
component-test:
|
||||
build_dir: *build_dir
|
||||
@@ -218,9 +220,11 @@ stretch: &stretch
|
||||
pre-cache:
|
||||
- sudo pip3 install --upgrade -r component-tests/requirements.txt
|
||||
post-cache:
|
||||
# Constructs config file from env vars
|
||||
- python3 component-tests/config.py
|
||||
# Creates and routes a Named Tunnel for this build. Also constructs config file from env vars.
|
||||
- python3 component-tests/setup.py --type create
|
||||
- pytest component-tests
|
||||
# The Named Tunnel is deleted and its route unprovisioned here.
|
||||
- python3 component-tests/setup.py --type cleanup
|
||||
update-homebrew:
|
||||
builddeps:
|
||||
- openssh-client
|
||||
|
||||
@@ -11,6 +11,12 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/net/idna"
|
||||
|
||||
"github.com/cloudflare/cloudflared/carrier"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
@@ -18,12 +24,6 @@ import (
|
||||
"github.com/cloudflare/cloudflared/sshgen"
|
||||
"github.com/cloudflare/cloudflared/token"
|
||||
"github.com/cloudflare/cloudflared/validation"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -387,7 +387,7 @@ func sshGen(c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sshgen.GenerateShortLivedCertificate(appInfo, cfdToken); err != nil {
|
||||
if err := sshgen.GenerateShortLivedCertificate(originURL, cfdToken); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/access"
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// ForwardServiceType is used to identify what kind of overwatch service this is
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/tunneldns"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/overwatch"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// AppService is the main service that runs when no command lines flags are passed to cloudflared
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package buildinfo
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type BuildInfo struct {
|
||||
|
||||
@@ -16,6 +16,6 @@ func RemovedCommand(name string) *cli.Command {
|
||||
)
|
||||
},
|
||||
Description: fmt.Sprintf("%s is deprecated", name),
|
||||
Hidden: true,
|
||||
Hidden: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,26 +32,8 @@ func setFlagsFromConfigFile(c *cli.Context) error {
|
||||
return cli.Exit(err, errorExitCode)
|
||||
}
|
||||
|
||||
if err := applyConfig(c, inputSource); err != nil {
|
||||
if err := altsrc.ApplyInputSource(c, inputSource); err != nil {
|
||||
return cli.Exit(err, errorExitCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyConfig(c *cli.Context, inputSource altsrc.InputSourceContext) error {
|
||||
for _, context := range c.Lineage() {
|
||||
if context.Command == nil {
|
||||
// we've reached the placeholder root context not associated with the app
|
||||
break
|
||||
}
|
||||
targetFlags := context.Command.Flags
|
||||
if context.Command.Name == "" {
|
||||
// commands that define child subcommands are executed as if they were an app
|
||||
targetFlags = context.App.Flags
|
||||
}
|
||||
if err := altsrc.ApplyInputSourceValues(context, inputSource, targetFlags); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func runApp(app *cli.App, graceShutdownC chan struct{}) {
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -6,6 +6,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/access"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/proxydns"
|
||||
@@ -16,11 +21,6 @@ import (
|
||||
"github.com/cloudflare/cloudflared/metrics"
|
||||
"github.com/cloudflare/cloudflared/overwatch"
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"os/exec"
|
||||
"text/template"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
)
|
||||
|
||||
@@ -12,6 +12,15 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-systemd/daemon"
|
||||
"github.com/facebookgo/grace/gracenet"
|
||||
"github.com/getsentry/raven-go"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/proxydns"
|
||||
@@ -27,15 +36,6 @@ import (
|
||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||
"github.com/cloudflare/cloudflared/tunneldns"
|
||||
"github.com/cloudflare/cloudflared/tunnelstore"
|
||||
|
||||
"github.com/coreos/go-systemd/daemon"
|
||||
"github.com/facebookgo/grace/gracenet"
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -8,6 +8,13 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
@@ -18,13 +25,6 @@ import (
|
||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/validation"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
const LogFieldOriginCertPath = "originCertPath"
|
||||
@@ -160,6 +160,10 @@ func prepareTunnelConfig(
|
||||
if err != nil {
|
||||
log.Err(err).Str(LogFieldHostname, configHostname).Msg("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 == ""
|
||||
clientID := c.String("id")
|
||||
|
||||
@@ -3,8 +3,9 @@ package tunnel
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelstore"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelstore"
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
|
||||
@@ -20,4 +20,4 @@ func waitForSignal(graceShutdownC chan struct{}, logger *zerolog.Logger) {
|
||||
close(graceShutdownC)
|
||||
case <-graceShutdownC:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,4 +101,3 @@ func TestWaitForShutdown(t *testing.T) {
|
||||
assert.True(t, contextCancelled)
|
||||
assert.True(t, time.Now().Sub(startTime) < time.Second) // check that wait ended early
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -147,7 +148,7 @@ func (sc *subcommandContext) readTunnelCredentials(credFinder CredFinder) (conne
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
func (sc *subcommandContext) create(name string, credentialsOutputPath string) (*tunnelstore.Tunnel, error) {
|
||||
func (sc *subcommandContext) create(name string, credentialsFilePath string) (*tunnelstore.Tunnel, error) {
|
||||
client, err := sc.client()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "couldn't create client to talk to Argo Tunnel backend")
|
||||
@@ -173,27 +174,40 @@ func (sc *subcommandContext) create(name string, credentialsOutputPath string) (
|
||||
TunnelID: tunnel.ID,
|
||||
TunnelName: name,
|
||||
}
|
||||
filePath, writeFileErr := writeTunnelCredentials(credential.certPath, credentialsOutputPath, &tunnelCredentials)
|
||||
usedCertPath := false
|
||||
if credentialsFilePath == "" {
|
||||
originCertDir := filepath.Dir(credential.certPath)
|
||||
credentialsFilePath, err = tunnelFilePath(tunnelCredentials.TunnelID, originCertDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
usedCertPath = true
|
||||
}
|
||||
writeFileErr := writeTunnelCredentials(credentialsFilePath, &tunnelCredentials)
|
||||
if writeFileErr != nil {
|
||||
var errorLines []string
|
||||
errorLines = append(errorLines, fmt.Sprintf("Your tunnel '%v' was created with ID %v. However, cloudflared couldn't write to the tunnel credentials file at %v.json.", tunnel.Name, tunnel.ID, tunnel.ID))
|
||||
errorLines = append(errorLines, fmt.Sprintf("Your tunnel '%v' was created with ID %v. However, cloudflared couldn't write tunnel credentials to %s.", tunnel.Name, tunnel.ID, credentialsFilePath))
|
||||
errorLines = append(errorLines, fmt.Sprintf("The file-writing error is: %v", writeFileErr))
|
||||
if deleteErr := client.DeleteTunnel(tunnel.ID); deleteErr != nil {
|
||||
errorLines = append(errorLines, fmt.Sprintf("Cloudflared tried to delete the tunnel for you, but encountered an error. You should use `cloudflared tunnel delete %v` to delete the tunnel yourself, because the tunnel can't be run without the tunnelfile.", tunnel.ID))
|
||||
errorLines = append(errorLines, fmt.Sprintf("The delete tunnel error is: %v", deleteErr))
|
||||
} else {
|
||||
errorLines = append(errorLines, fmt.Sprintf("The tunnel was deleted, because the tunnel can't be run without the tunnelfile"))
|
||||
errorLines = append(errorLines, fmt.Sprintf("The tunnel was deleted, because the tunnel can't be run without the credentials file"))
|
||||
}
|
||||
errorMsg := strings.Join(errorLines, "\n")
|
||||
return nil, errors.New(errorMsg)
|
||||
}
|
||||
sc.log.Info().Msgf("Tunnel credentials written to %v. cloudflared chose this file based on where your origin certificate was found. Keep this file secret. To revoke these credentials, delete the tunnel.", filePath)
|
||||
|
||||
if outputFormat := sc.c.String(outputFormatFlag.Name); outputFormat != "" {
|
||||
return nil, renderOutput(outputFormat, &tunnel)
|
||||
}
|
||||
|
||||
sc.log.Info().Msgf("Created tunnel %s with id %s", tunnel.Name, tunnel.ID)
|
||||
fmt.Printf("Tunnel credentials written to %v.", credentialsFilePath)
|
||||
if usedCertPath {
|
||||
fmt.Print(" cloudflared chose this file based on where your origin certificate was found.")
|
||||
}
|
||||
fmt.Println(" Keep this file secret. To revoke these credentials, delete the tunnel.")
|
||||
fmt.Printf("\nCreated tunnel %s with id %s\n", tunnel.Name, tunnel.ID)
|
||||
return tunnel, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ package tunnel
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/cloudflare/cloudflared/teamnet"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cloudflare/cloudflared/teamnet"
|
||||
)
|
||||
|
||||
const noClientMsg = "error while creating backend client"
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/tunnelstore"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Test_findIDs(t *testing.T) {
|
||||
|
||||
@@ -14,12 +14,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
"golang.org/x/net/idna"
|
||||
"gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
|
||||
@@ -94,13 +94,14 @@ var (
|
||||
Aliases: []string{"F"},
|
||||
Usage: "Opt into various features that are still being developed or tested.",
|
||||
})
|
||||
credentialsFileFlag = altsrc.NewStringFlag(&cli.StringFlag{
|
||||
credentialsFileFlagCLIOnly = &cli.StringFlag{
|
||||
Name: CredFileFlag,
|
||||
Aliases: []string{CredFileFlagAlias},
|
||||
Usage: "Filepath at which to read/write the tunnel credentials",
|
||||
EnvVars: []string{"TUNNEL_CRED_FILE"},
|
||||
})
|
||||
forceDeleteFlag = &cli.BoolFlag{
|
||||
}
|
||||
credentialsFileFlag = altsrc.NewStringFlag(credentialsFileFlagCLIOnly)
|
||||
forceDeleteFlag = &cli.BoolFlag{
|
||||
Name: "force",
|
||||
Aliases: []string{"f"},
|
||||
Usage: "Cleans up any stale connections before the tunnel is deleted. cloudflared will not " +
|
||||
@@ -146,7 +147,7 @@ func buildCreateCommand() *cli.Command {
|
||||
For example, to create a tunnel named 'my-tunnel' run:
|
||||
|
||||
$ cloudflared tunnel create my-tunnel`,
|
||||
Flags: []cli.Flag{outputFormatFlag, credentialsFileFlag},
|
||||
Flags: []cli.Flag{outputFormatFlag, credentialsFileFlagCLIOnly},
|
||||
CustomHelpTemplate: commandHelpTemplate(),
|
||||
}
|
||||
}
|
||||
@@ -182,27 +183,20 @@ func tunnelFilePath(tunnelID uuid.UUID, directory string) (string, error) {
|
||||
return homedir.Expand(filePath)
|
||||
}
|
||||
|
||||
// If an `outputFile` is given, write the credentials there.
|
||||
// Otherwise, write it to the same directory as the originCert,
|
||||
// with the filename `<tunnel id>.json`.
|
||||
func writeTunnelCredentials(
|
||||
originCertPath, outputFile string,
|
||||
credentials *connection.Credentials,
|
||||
) (filePath string, err error) {
|
||||
filePath = outputFile
|
||||
if outputFile == "" {
|
||||
originCertDir := filepath.Dir(originCertPath)
|
||||
filePath, err = tunnelFilePath(credentials.TunnelID, originCertDir)
|
||||
// writeTunnelCredentials saves `credentials` as a JSON into `filePath`, only if
|
||||
// the file does not exist already
|
||||
func writeTunnelCredentials(filePath string, credentials *connection.Credentials) error {
|
||||
if _, err := os.Stat(filePath); !os.IsNotExist(err) {
|
||||
if err == nil {
|
||||
return fmt.Errorf("%s already exists", filePath)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Write the name and ID to the file too
|
||||
body, err := json.Marshal(credentials)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Unable to marshal tunnel credentials to JSON")
|
||||
return errors.Wrap(err, "Unable to marshal tunnel credentials to JSON")
|
||||
}
|
||||
return filePath, ioutil.WriteFile(filePath, body, 400)
|
||||
return ioutil.WriteFile(filePath, body, 400)
|
||||
}
|
||||
|
||||
func buildListCommand() *cli.Command {
|
||||
@@ -501,7 +495,7 @@ func buildDeleteCommand() *cli.Command {
|
||||
Usage: "Delete existing tunnel by UUID or name",
|
||||
UsageText: "cloudflared tunnel [tunnel command options] delete [subcommand options] TUNNEL",
|
||||
Description: "cloudflared tunnel delete will delete tunnels with the given tunnel UUIDs or names. A tunnel cannot be deleted if it has active connections. To delete the tunnel unconditionally, use -f flag.",
|
||||
Flags: []cli.Flag{credentialsFileFlag, forceDeleteFlag},
|
||||
Flags: []cli.Flag{credentialsFileFlagCLIOnly, forceDeleteFlag},
|
||||
CustomHelpTemplate: commandHelpTemplate(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelstore"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelstore"
|
||||
)
|
||||
|
||||
func Test_fmtConnections(t *testing.T) {
|
||||
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
)
|
||||
|
||||
type connState struct {
|
||||
|
||||
@@ -13,15 +13,14 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
"golang.org/x/sys/windows/svc/eventlog"
|
||||
"golang.org/x/sys/windows/svc/mgr"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
import base64
|
||||
import copy
|
||||
import os
|
||||
import yaml
|
||||
|
||||
from dataclasses import dataclass, InitVar
|
||||
|
||||
from constants import METRICS_PORT
|
||||
|
||||
from util import LOGGER
|
||||
|
||||
# frozen=True raises exception when assigning to fields. This emulates immutability
|
||||
|
||||
|
||||
@@ -98,35 +93,3 @@ class ClassicTunnelConfig(ClassicTunnelBaseConfig):
|
||||
|
||||
def get_url(self):
|
||||
return "https://" + self.hostname
|
||||
|
||||
|
||||
def build_config_from_env():
|
||||
config_path = get_env("COMPONENT_TESTS_CONFIG")
|
||||
config_content = base64.b64decode(
|
||||
get_env("COMPONENT_TESTS_CONFIG_CONTENT")).decode('utf-8')
|
||||
config_yaml = yaml.safe_load(config_content)
|
||||
|
||||
credentials_file = get_env("COMPONENT_TESTS_CREDENTIALS_FILE")
|
||||
write_file(credentials_file, config_yaml["credentials_file"])
|
||||
|
||||
origincert = get_env("COMPONENT_TESTS_ORIGINCERT")
|
||||
write_file(origincert, config_yaml["origincert"])
|
||||
|
||||
write_file(config_content, config_path)
|
||||
|
||||
|
||||
def write_file(content, path):
|
||||
with open(path, 'w') as outfile:
|
||||
outfile.write(content)
|
||||
outfile.close
|
||||
|
||||
|
||||
def get_env(env_name):
|
||||
val = os.getenv(env_name)
|
||||
if val is None:
|
||||
raise Exception(f"{env_name} is not set")
|
||||
return val
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
build_config_from_env()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
cloudflare==2.8.15
|
||||
flaky==3.7.0
|
||||
pytest==6.2.2
|
||||
pyyaml==5.4.1
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python
|
||||
import argparse
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import uuid
|
||||
|
||||
import CloudFlare
|
||||
import yaml
|
||||
from retrying import retry
|
||||
|
||||
from constants import MAX_RETRIES, BACKOFF_SECS
|
||||
from util import LOGGER
|
||||
|
||||
|
||||
def get_config_from_env():
|
||||
config_content = base64.b64decode(get_env("COMPONENT_TESTS_CONFIG_CONTENT")).decode('utf-8')
|
||||
return yaml.safe_load(config_content)
|
||||
|
||||
|
||||
def get_config_from_file():
|
||||
config_path = get_env("COMPONENT_TESTS_CONFIG")
|
||||
with open(config_path, 'r') as infile:
|
||||
return yaml.safe_load(infile)
|
||||
|
||||
|
||||
def persist_config(config):
|
||||
config_path = get_env("COMPONENT_TESTS_CONFIG")
|
||||
with open(config_path, 'w') as outfile:
|
||||
yaml.safe_dump(config, outfile)
|
||||
|
||||
|
||||
def persist_origin_cert(config):
|
||||
origincert = get_env("COMPONENT_TESTS_ORIGINCERT")
|
||||
path = config["origincert"]
|
||||
with open(path, 'w') as outfile:
|
||||
outfile.write(origincert)
|
||||
return path
|
||||
|
||||
|
||||
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||
def create_tunnel(config, origincert_path, random_uuid):
|
||||
# Delete any previous existing credentials file. If the agent keeps files around (that's the case in Windows) then
|
||||
# cloudflared tunnel create will refuse to create the tunnel because it does not want to overwrite credentials
|
||||
# files.
|
||||
credentials_path = config["credentials_file"]
|
||||
try:
|
||||
os.remove(credentials_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
tunnel_name = "cfd_component_test-" + random_uuid
|
||||
create_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", origincert_path, "create",
|
||||
"--credentials-file", credentials_path, tunnel_name]
|
||||
LOGGER.info(f"Creating tunnel with {create_cmd}")
|
||||
subprocess.run(create_cmd, check=True)
|
||||
|
||||
list_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", origincert_path, "list", "--name",
|
||||
tunnel_name, "--output", "json"]
|
||||
LOGGER.info(f"Listing tunnel with {list_cmd}")
|
||||
cloudflared = subprocess.run(list_cmd, check=True, capture_output=True)
|
||||
return json.loads(cloudflared.stdout)[0]["id"]
|
||||
|
||||
|
||||
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||
def delete_tunnel(config):
|
||||
credentials_path = config["credentials_file"]
|
||||
delete_cmd = [config["cloudflared_binary"], "tunnel", "--origincert", config["origincert"], "delete",
|
||||
"--credentials-file", credentials_path, "-f", config["tunnel"]]
|
||||
LOGGER.info(f"Deleting tunnel with {delete_cmd}")
|
||||
subprocess.run(delete_cmd, check=True)
|
||||
|
||||
|
||||
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||
def create_dns(config, hostname, type, content):
|
||||
cf = CloudFlare.CloudFlare(debug=True, token=get_env("DNS_API_TOKEN"))
|
||||
cf.zones.dns_records.post(
|
||||
config["zone_tag"],
|
||||
data={'name': hostname, 'type': type, 'content': content, 'proxied': True}
|
||||
)
|
||||
|
||||
|
||||
def create_classic_dns(config, random_uuid):
|
||||
classic_hostname = "classic-" + random_uuid + "." + config["zone_domain"]
|
||||
create_dns(config, classic_hostname, "AAAA", "fd10:aec2:5dae::")
|
||||
return classic_hostname
|
||||
|
||||
|
||||
def create_named_dns(config, random_uuid):
|
||||
hostname = "named-" + random_uuid + "." + config["zone_domain"]
|
||||
create_dns(config, hostname, "CNAME", config["tunnel"] + ".cfargotunnel.com")
|
||||
return hostname
|
||||
|
||||
|
||||
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||
def delete_dns(config, hostname):
|
||||
cf = CloudFlare.CloudFlare(debug=True, token=get_env("DNS_API_TOKEN"))
|
||||
zone_tag = config["zone_tag"]
|
||||
dns_records = cf.zones.dns_records.get(zone_tag, params={'name': hostname})
|
||||
if len(dns_records) > 0:
|
||||
cf.zones.dns_records.delete(zone_tag, dns_records[0]['id'])
|
||||
|
||||
|
||||
def write_file(content, path):
|
||||
with open(path, 'w') as outfile:
|
||||
outfile.write(content)
|
||||
|
||||
|
||||
def get_env(env_name):
|
||||
val = os.getenv(env_name)
|
||||
if val is None:
|
||||
raise Exception(f"{env_name} is not set")
|
||||
return val
|
||||
|
||||
|
||||
def create():
|
||||
"""
|
||||
Creates the necessary resources for the components test to run.
|
||||
- Creates a named tunnel with a random name.
|
||||
- Creates a random CNAME DNS entry for that tunnel.
|
||||
- Creates a random AAAA DNS entry for a classic tunnel.
|
||||
|
||||
Those created resources are added to the config (obtained from an environment variable).
|
||||
The resulting configuration is persisted for the tests to use.
|
||||
"""
|
||||
config = get_config_from_env()
|
||||
origincert_path = persist_origin_cert(config)
|
||||
|
||||
random_uuid = str(uuid.uuid4())
|
||||
config["tunnel"] = create_tunnel(config, origincert_path, random_uuid)
|
||||
config["classic_hostname"] = create_classic_dns(config, random_uuid)
|
||||
config["ingress"] = [
|
||||
{
|
||||
"hostname": create_named_dns(config, random_uuid),
|
||||
"service": "hello_world"
|
||||
},
|
||||
{
|
||||
"service": "http_status:404"
|
||||
}
|
||||
]
|
||||
|
||||
persist_config(config)
|
||||
|
||||
|
||||
def cleanup():
|
||||
"""
|
||||
Reads the persisted configuration that was created previously.
|
||||
Deletes the resources that were created there.
|
||||
"""
|
||||
config = get_config_from_file()
|
||||
delete_tunnel(config)
|
||||
delete_dns(config, config["classic_hostname"])
|
||||
delete_dns(config, config["ingress"][0]["hostname"])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='setup component tests')
|
||||
parser.add_argument('--type', choices=['create', 'cleanup'], default='create')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.type == 'create':
|
||||
create()
|
||||
else:
|
||||
cleanup()
|
||||
@@ -1,24 +1,78 @@
|
||||
#!/usr/bin/env python
|
||||
import json
|
||||
import os
|
||||
from util import start_cloudflared, wait_tunnel_ready, send_requests, LOGGER
|
||||
|
||||
from util import start_cloudflared, wait_tunnel_ready, send_requests
|
||||
|
||||
# Rolling logger rotate log files after 1 MB
|
||||
rotate_after_size = 1000 * 1000
|
||||
default_log_file = "cloudflared.log"
|
||||
expect_message = "Starting tunnel"
|
||||
|
||||
|
||||
def assert_log_to_terminal(cloudflared):
|
||||
stderr = cloudflared.stderr.read(200)
|
||||
# cloudflared logs the following when it first starts
|
||||
# 2021-03-10T12:30:39Z INF Starting tunnel tunnelID=<tunnel ID>
|
||||
assert expect_message.encode() in stderr, f"{stderr} doesn't contain {expect_message}"
|
||||
|
||||
|
||||
def assert_log_in_file(file):
|
||||
with open(file, "r") as f:
|
||||
log = f.read(200)
|
||||
# cloudflared logs the following when it first starts
|
||||
# {"level":"info","tunnelID":"<tunnel ID>","time":"2021-03-10T12:21:13Z","message":"Starting tunnel"}
|
||||
assert expect_message in log, f"{log} doesn't contain {expect_message}"
|
||||
|
||||
|
||||
def assert_json_log(file):
|
||||
def assert_in_json(j, key):
|
||||
assert key in j, f"{key} is not in j"
|
||||
|
||||
with open(file, "r") as f:
|
||||
line = f.readline()
|
||||
json_log = json.loads(line)
|
||||
assert_in_json(json_log, "level")
|
||||
assert_in_json(json_log, "time")
|
||||
assert_in_json(json_log, "message")
|
||||
|
||||
|
||||
def assert_log_to_dir(config, log_dir):
|
||||
max_batches = 3
|
||||
batch_requests = 1000
|
||||
for _ in range(max_batches):
|
||||
send_requests(config.get_url(),
|
||||
batch_requests, require_ok=False)
|
||||
files = os.listdir(log_dir)
|
||||
if len(files) == 2:
|
||||
current_log_file_index = files.index(default_log_file)
|
||||
current_file = log_dir / files[current_log_file_index]
|
||||
stats = os.stat(current_file)
|
||||
assert stats.st_size > 0
|
||||
assert_json_log(current_file)
|
||||
|
||||
# One file is the current log file, the other is the rotated log file
|
||||
rotated_log_file_index = 0 if current_log_file_index == 1 else 1
|
||||
rotated_file = log_dir / files[rotated_log_file_index]
|
||||
stats = os.stat(rotated_file)
|
||||
assert stats.st_size > rotate_after_size
|
||||
assert_log_in_file(rotated_file)
|
||||
assert_json_log(current_file)
|
||||
return
|
||||
|
||||
raise Exception(
|
||||
f"Log file isn't rotated after sending {max_batches * batch_requests} requests")
|
||||
|
||||
|
||||
class TestLogging:
|
||||
# TODO: Test logging when running as a service https://jira.cfops.it/browse/TUN-4082
|
||||
# Rolling logger rotate log files after 1 MB
|
||||
rotate_after_size = 1000 * 1000
|
||||
default_log_file = "cloudflared.log"
|
||||
expect_message = "Starting tunnel"
|
||||
|
||||
def test_logging_to_terminal(self, tmp_path, component_tests_config):
|
||||
config = component_tests_config()
|
||||
with start_cloudflared(tmp_path, config, new_process=True) as cloudflared:
|
||||
wait_tunnel_ready(tunnel_url=config.get_url())
|
||||
self.assert_log_to_terminal(cloudflared)
|
||||
assert_log_to_terminal(cloudflared)
|
||||
|
||||
def test_logging_to_file(self, tmp_path, component_tests_config):
|
||||
log_file = tmp_path / self.default_log_file
|
||||
log_file = tmp_path / default_log_file
|
||||
extra_config = {
|
||||
# Convert from pathlib.Path to str
|
||||
"logfile": str(log_file),
|
||||
@@ -26,8 +80,8 @@ class TestLogging:
|
||||
config = component_tests_config(extra_config)
|
||||
with start_cloudflared(tmp_path, config, new_process=True, capture_output=False):
|
||||
wait_tunnel_ready(tunnel_url=config.get_url())
|
||||
self.assert_log_in_file(log_file)
|
||||
self.assert_json_log(log_file)
|
||||
assert_log_in_file(log_file)
|
||||
assert_json_log(log_file)
|
||||
|
||||
def test_logging_to_dir(self, tmp_path, component_tests_config):
|
||||
log_dir = tmp_path / "logs"
|
||||
@@ -39,55 +93,4 @@ class TestLogging:
|
||||
config = component_tests_config(extra_config)
|
||||
with start_cloudflared(tmp_path, config, new_process=True, capture_output=False):
|
||||
wait_tunnel_ready(tunnel_url=config.get_url())
|
||||
self.assert_log_to_dir(config, log_dir)
|
||||
|
||||
def assert_log_to_terminal(self, cloudflared):
|
||||
stderr = cloudflared.stderr.read(200)
|
||||
# cloudflared logs the following when it first starts
|
||||
# 2021-03-10T12:30:39Z INF Starting tunnel tunnelID=<tunnel ID>
|
||||
assert self.expect_message.encode(
|
||||
) in stderr, f"{stderr} doesn't contain {self.expect_message}"
|
||||
|
||||
def assert_log_in_file(self, file, expect_message=""):
|
||||
with open(file, "r") as f:
|
||||
log = f.read(200)
|
||||
# cloudflared logs the following when it first starts
|
||||
# {"level":"info","tunnelID":"<tunnel ID>","time":"2021-03-10T12:21:13Z","message":"Starting tunnel"}
|
||||
assert self.expect_message in log, f"{log} doesn't contain {self.expect_message}"
|
||||
|
||||
def assert_json_log(self, file):
|
||||
with open(file, "r") as f:
|
||||
line = f.readline()
|
||||
json_log = json.loads(line)
|
||||
self.assert_in_json(json_log, "level")
|
||||
self.assert_in_json(json_log, "time")
|
||||
self.assert_in_json(json_log, "message")
|
||||
|
||||
def assert_in_json(self, j, key):
|
||||
assert key in j, f"{key} is not in j"
|
||||
|
||||
def assert_log_to_dir(self, config, log_dir):
|
||||
max_batches = 3
|
||||
batch_requests = 1000
|
||||
for _ in range(max_batches):
|
||||
send_requests(config.get_url(),
|
||||
batch_requests, require_ok=False)
|
||||
files = os.listdir(log_dir)
|
||||
if len(files) == 2:
|
||||
current_log_file_index = files.index(self.default_log_file)
|
||||
current_file = log_dir / files[current_log_file_index]
|
||||
stats = os.stat(current_file)
|
||||
assert stats.st_size > 0
|
||||
self.assert_json_log(current_file)
|
||||
|
||||
# One file is the current log file, the other is the rotated log file
|
||||
rotated_log_file_index = 0 if current_log_file_index == 1 else 1
|
||||
rotated_file = log_dir / files[rotated_log_file_index]
|
||||
stats = os.stat(rotated_file)
|
||||
assert stats.st_size > self.rotate_after_size
|
||||
self.assert_log_in_file(rotated_file)
|
||||
self.assert_json_log(current_file)
|
||||
return
|
||||
|
||||
raise Exception(
|
||||
f"Log file isn't rotated after sending {max_batches * batch_requests} requests")
|
||||
assert_log_to_dir(config, log_dir)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
import copy
|
||||
from flaky import flaky
|
||||
from retrying import retry
|
||||
from time import sleep
|
||||
|
||||
from util import start_cloudflared, wait_tunnel_ready, check_tunnel_not_connected, send_requests
|
||||
from flaky import flaky
|
||||
|
||||
from util import start_cloudflared, wait_tunnel_ready, check_tunnel_not_connected
|
||||
|
||||
|
||||
@flaky(max_runs=3, min_passes=1)
|
||||
@@ -17,7 +17,7 @@ class TestReconnect():
|
||||
|
||||
def test_named_reconnect(self, tmp_path, component_tests_config):
|
||||
config = component_tests_config(self.extra_config)
|
||||
with start_cloudflared(tmp_path, config, new_process=True, allow_input=True) as cloudflared:
|
||||
with start_cloudflared(tmp_path, config, new_process=True, allow_input=True, capture_output=False) as cloudflared:
|
||||
# Repeat the test multiple times because some issues only occur after multiple reconnects
|
||||
self.assert_reconnect(config, cloudflared, 5)
|
||||
|
||||
@@ -26,7 +26,7 @@ class TestReconnect():
|
||||
extra_config["hello-world"] = True
|
||||
config = component_tests_config(
|
||||
additional_config=extra_config, named_tunnel=False)
|
||||
with start_cloudflared(tmp_path, config, cfd_args=[], new_process=True, allow_input=True) as cloudflared:
|
||||
with start_cloudflared(tmp_path, config, cfd_args=[], new_process=True, allow_input=True, capture_output=False) as cloudflared:
|
||||
self.assert_reconnect(config, cloudflared, 1)
|
||||
|
||||
def send_reconnect(self, cloudflared, secs):
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import pytest
|
||||
import subprocess
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from util import start_cloudflared, cloudflared_cmd, wait_tunnel_ready, LOGGER
|
||||
import pytest
|
||||
|
||||
import test_logging
|
||||
from util import start_cloudflared, wait_tunnel_ready
|
||||
|
||||
|
||||
def select_platform(plat):
|
||||
@@ -22,34 +24,94 @@ def default_config_file():
|
||||
return os.path.join(default_config_dir(), "config.yml")
|
||||
|
||||
|
||||
class TestServiceMode():
|
||||
class TestServiceMode:
|
||||
@select_platform("Darwin")
|
||||
@pytest.mark.skipif(os.path.exists(default_config_file()), reason=f"There is already a config file in default path")
|
||||
def test_launchd_service(self, component_tests_config):
|
||||
# On Darwin cloudflared service defaults to run classic tunnel command
|
||||
def test_launchd_service_log_to_file(self, tmp_path, component_tests_config):
|
||||
log_file = tmp_path / test_logging.default_log_file
|
||||
additional_config = {
|
||||
# On Darwin cloudflared service defaults to run classic tunnel command
|
||||
"hello-world": True,
|
||||
"logfile": str(log_file),
|
||||
}
|
||||
config = component_tests_config(
|
||||
additional_config=additional_config, named_tunnel=False)
|
||||
config = component_tests_config(additional_config=additional_config, named_tunnel=False)
|
||||
|
||||
def assert_log_file():
|
||||
test_logging.assert_log_in_file(log_file)
|
||||
test_logging.assert_json_log(log_file)
|
||||
|
||||
self.launchd_service_scenario(config, assert_log_file)
|
||||
|
||||
@select_platform("Darwin")
|
||||
@pytest.mark.skipif(os.path.exists(default_config_file()), reason=f"There is already a config file in default path")
|
||||
def test_launchd_service_rotating_log(self, tmp_path, component_tests_config):
|
||||
log_dir = tmp_path / "logs"
|
||||
additional_config = {
|
||||
# On Darwin cloudflared service defaults to run classic tunnel command
|
||||
"hello-world": True,
|
||||
"loglevel": "debug",
|
||||
"log-directory": str(log_dir),
|
||||
}
|
||||
config = component_tests_config(additional_config=additional_config, named_tunnel=False)
|
||||
|
||||
def assert_rotating_log():
|
||||
test_logging.assert_log_to_dir(config, log_dir)
|
||||
|
||||
self.launchd_service_scenario(config, assert_rotating_log)
|
||||
|
||||
def launchd_service_scenario(self, config, extra_assertions):
|
||||
with self.run_service(Path(default_config_dir()), config):
|
||||
self.launchctl_cmd("list")
|
||||
self.launchctl_cmd("start")
|
||||
wait_tunnel_ready(tunnel_url=config.get_url())
|
||||
extra_assertions()
|
||||
self.launchctl_cmd("stop")
|
||||
|
||||
os.remove(default_config_file())
|
||||
self.launchctl_cmd("list", success=False)
|
||||
|
||||
@select_platform("Linux")
|
||||
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"), reason=f"There is already a config file in default path")
|
||||
def test_sysv_service(self, tmp_path, component_tests_config):
|
||||
config = component_tests_config()
|
||||
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
|
||||
reason=f"There is already a config file in default path")
|
||||
def test_sysv_service_log_to_file(self, tmp_path, component_tests_config):
|
||||
log_file = tmp_path / test_logging.default_log_file
|
||||
additional_config = {
|
||||
"logfile": str(log_file),
|
||||
}
|
||||
config = component_tests_config(additional_config=additional_config)
|
||||
|
||||
def assert_log_file():
|
||||
test_logging.assert_log_in_file(log_file)
|
||||
test_logging.assert_json_log(log_file)
|
||||
|
||||
self.sysv_service_scenario(config, tmp_path, assert_log_file)
|
||||
|
||||
@select_platform("Linux")
|
||||
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
|
||||
reason=f"There is already a config file in default path")
|
||||
def test_sysv_service_rotating_log(self, tmp_path, component_tests_config):
|
||||
log_dir = tmp_path / "logs"
|
||||
additional_config = {
|
||||
"loglevel": "debug",
|
||||
"log-directory": str(log_dir),
|
||||
}
|
||||
config = component_tests_config(additional_config=additional_config)
|
||||
|
||||
def assert_rotating_log():
|
||||
# We need the folder to have executable permissions for the "stat" command in the assertions to work.
|
||||
subprocess.check_call(['sudo', 'chmod', 'o+x', log_dir])
|
||||
test_logging.assert_log_to_dir(config, log_dir)
|
||||
|
||||
self.sysv_service_scenario(config, tmp_path, assert_rotating_log)
|
||||
|
||||
def sysv_service_scenario(self, config, tmp_path, extra_assertions):
|
||||
with self.run_service(tmp_path, config, root=True):
|
||||
self.sysv_cmd("start")
|
||||
self.sysv_cmd("status")
|
||||
wait_tunnel_ready(tunnel_url=config.get_url())
|
||||
extra_assertions()
|
||||
self.sysv_cmd("stop")
|
||||
|
||||
# Service install copies config file to /etc/cloudflared/config.yml
|
||||
subprocess.run(["sudo", "rm", "/etc/cloudflared/config.yml"])
|
||||
self.sysv_cmd("status", success=False)
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
from contextlib import contextmanager
|
||||
import requests
|
||||
import platform
|
||||
import signal
|
||||
import threading
|
||||
import time
|
||||
|
||||
from util import start_cloudflared, wait_tunnel_ready, check_tunnel_not_connected, LOGGER
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from util import start_cloudflared, wait_tunnel_ready, check_tunnel_not_connected
|
||||
|
||||
|
||||
def supported_signals():
|
||||
if platform.system() == "Windows":
|
||||
return [signal.SIGTERM]
|
||||
return [signal.SIGTERM, signal.SIGINT]
|
||||
|
||||
|
||||
class TestTermination():
|
||||
@@ -14,57 +23,56 @@ class TestTermination():
|
||||
extra_config = {
|
||||
"grace-period": f"{grace_period}s",
|
||||
}
|
||||
signals = [signal.SIGTERM, signal.SIGINT]
|
||||
sse_endpoint = "/sse?freq=1s"
|
||||
|
||||
def test_graceful_shutdown(self, tmp_path, component_tests_config):
|
||||
@pytest.mark.parametrize("signal", supported_signals())
|
||||
def test_graceful_shutdown(self, tmp_path, component_tests_config, signal):
|
||||
config = component_tests_config(self.extra_config)
|
||||
for sig in self.signals:
|
||||
with start_cloudflared(
|
||||
tmp_path, config, new_process=True, capture_output=False) as cloudflared:
|
||||
wait_tunnel_ready(tunnel_url=config.get_url())
|
||||
with start_cloudflared(
|
||||
tmp_path, config, new_process=True, capture_output=False) as cloudflared:
|
||||
wait_tunnel_ready(tunnel_url=config.get_url())
|
||||
|
||||
connected = threading.Condition()
|
||||
in_flight_req = threading.Thread(
|
||||
target=self.stream_request, args=(config, connected,))
|
||||
in_flight_req.start()
|
||||
connected = threading.Condition()
|
||||
in_flight_req = threading.Thread(
|
||||
target=self.stream_request, args=(config, connected, False, ))
|
||||
in_flight_req.start()
|
||||
|
||||
with connected:
|
||||
connected.wait(self.timeout)
|
||||
# Send signal after the SSE connection is established
|
||||
self.terminate_by_signal(cloudflared, sig)
|
||||
self.wait_eyeball_thread(
|
||||
in_flight_req, self.grace_period + self.timeout)
|
||||
with connected:
|
||||
connected.wait(self.timeout)
|
||||
# Send signal after the SSE connection is established
|
||||
self.terminate_by_signal(cloudflared, signal)
|
||||
self.wait_eyeball_thread(
|
||||
in_flight_req, self.grace_period + self.timeout)
|
||||
|
||||
# test cloudflared terminates before grace period expires when all eyeball
|
||||
# connections are drained
|
||||
def test_shutdown_once_no_connection(self, tmp_path, component_tests_config):
|
||||
@pytest.mark.parametrize("signal", supported_signals())
|
||||
def test_shutdown_once_no_connection(self, tmp_path, component_tests_config, signal):
|
||||
config = component_tests_config(self.extra_config)
|
||||
for sig in self.signals:
|
||||
with start_cloudflared(
|
||||
tmp_path, config, new_process=True, capture_output=False) as cloudflared:
|
||||
wait_tunnel_ready(tunnel_url=config.get_url())
|
||||
with start_cloudflared(
|
||||
tmp_path, config, new_process=True, capture_output=False) as cloudflared:
|
||||
wait_tunnel_ready(tunnel_url=config.get_url())
|
||||
|
||||
connected = threading.Condition()
|
||||
in_flight_req = threading.Thread(
|
||||
target=self.stream_request, args=(config, connected, True, ))
|
||||
in_flight_req.start()
|
||||
connected = threading.Condition()
|
||||
in_flight_req = threading.Thread(
|
||||
target=self.stream_request, args=(config, connected, True, ))
|
||||
in_flight_req.start()
|
||||
|
||||
with connected:
|
||||
connected.wait(self.timeout)
|
||||
with self.within_grace_period():
|
||||
# Send signal after the SSE connection is established
|
||||
self.terminate_by_signal(cloudflared, sig)
|
||||
self.wait_eyeball_thread(in_flight_req, self.grace_period)
|
||||
with connected:
|
||||
connected.wait(self.timeout)
|
||||
with self.within_grace_period():
|
||||
# Send signal after the SSE connection is established
|
||||
self.terminate_by_signal(cloudflared, signal)
|
||||
self.wait_eyeball_thread(in_flight_req, self.grace_period)
|
||||
|
||||
def test_no_connection_shutdown(self, tmp_path, component_tests_config):
|
||||
@pytest.mark.parametrize("signal", supported_signals())
|
||||
def test_no_connection_shutdown(self, tmp_path, component_tests_config, signal):
|
||||
config = component_tests_config(self.extra_config)
|
||||
for sig in self.signals:
|
||||
with start_cloudflared(
|
||||
tmp_path, config, new_process=True, capture_output=False) as cloudflared:
|
||||
wait_tunnel_ready(tunnel_url=config.get_url())
|
||||
with self.within_grace_period():
|
||||
self.terminate_by_signal(cloudflared, sig)
|
||||
with start_cloudflared(
|
||||
tmp_path, config, new_process=True, capture_output=False) as cloudflared:
|
||||
wait_tunnel_ready(tunnel_url=config.get_url())
|
||||
with self.within_grace_period():
|
||||
self.terminate_by_signal(cloudflared, signal)
|
||||
|
||||
def terminate_by_signal(self, cloudflared, sig):
|
||||
cloudflared.send_signal(sig)
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/validation"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestConfigFileSettings(t *testing.T) {
|
||||
|
||||
+3
-3
@@ -4,11 +4,11 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
)
|
||||
|
||||
// Notifier sends out config updates
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
)
|
||||
|
||||
type mockNotifier struct {
|
||||
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
const LogFieldConnIndex = "connIndex"
|
||||
|
||||
+4
-4
@@ -7,13 +7,13 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -38,7 +38,7 @@ func newH2MuxConnection(t require.TestingT) (*h2muxConnection, *h2mux.Muxer) {
|
||||
// we only expect RPC traffic in client->edge direction, provide minimal support for mocking
|
||||
require.True(t, stream.IsRPCStream())
|
||||
return stream.WriteHeaders([]h2mux.Header{
|
||||
{Name: ":status", Value: "200"},
|
||||
{Name: ":status", Value: "200"},
|
||||
})
|
||||
}),
|
||||
}
|
||||
|
||||
+3
-3
@@ -10,11 +10,11 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -12,15 +12,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -414,4 +415,4 @@ func newTunnelMetrics() *tunnelMetrics {
|
||||
tunnelMetricsInternal.metrics = initTunnelMetrics()
|
||||
})
|
||||
return tunnelMetricsInternal.metrics
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -67,10 +67,9 @@ func TestObserverEventsDontBlock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type eventCollectorSink struct {
|
||||
observedEvents []Event
|
||||
mu sync.Mutex
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (s *eventCollectorSink) OnTunnelEvent(event Event) {
|
||||
@@ -83,4 +82,4 @@ func (s *eventCollectorSink) assertSawEvent(t *testing.T, event Event) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
assert.Contains(t, s.observedEvents, event)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -6,11 +6,11 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
type tunnelServerClient struct {
|
||||
|
||||
@@ -5,8 +5,9 @@ import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
OUTPUT=$(for d in $(go list -mod=vendor -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc) ; do goimports -format-only -local github.com/cloudflare/cloudflared -d $d ; done)
|
||||
|
||||
if [ -n "$OUTPUT" ] ; then
|
||||
PAGER=$(which colordiff || echo cat)
|
||||
echo
|
||||
echo "Code formatting issues found, use 'goimports -format-only -local github.com/cloudflare/cloudflared' to correct them"
|
||||
echo
|
||||
echo "$OUTPUT" | $PAGER
|
||||
exit 1
|
||||
fi
|
||||
@@ -9,6 +9,7 @@ require (
|
||||
github.com/coredns/coredns v1.7.0
|
||||
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
|
||||
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 // indirect
|
||||
github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
|
||||
@@ -39,6 +40,7 @@ require (
|
||||
github.com/prometheus/common v0.13.0 // indirect
|
||||
github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92
|
||||
github.com/rs/zerolog v1.20.0
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/stretchr/testify v1.6.0
|
||||
github.com/urfave/cli/v2 v2.2.0
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
|
||||
@@ -58,4 +60,4 @@ require (
|
||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
||||
)
|
||||
|
||||
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210223162147-c8b4f97735e4
|
||||
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
|
||||
|
||||
@@ -140,8 +140,9 @@ github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+
|
||||
github.com/coreos/license-bill-of-materials v0.0.0-20190913234955-13baff47494e/go.mod h1:4xMOusJ7xxc84WclVxKT8+lNfGYDwojOUC2OQNCwcj4=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpu/goacmedns v0.0.1/go.mod h1:sesf/pNnCYwUevQEQfEwY0Y3DydlQWSGZbaMElOWxok=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -347,8 +348,8 @@ github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/infobloxopen/go-trees v0.0.0-20190313150506-2af4e13f9062/go.mod h1:PcNJqIlcX/dj3DTG/+QQnRvSgTMG6CLpRMjWcv4+J6w=
|
||||
github.com/ipostelnik/cli/v2 v2.3.1-0.20210223162147-c8b4f97735e4 h1:U2OxGKZHC5puhKZnGo6wWWjuWusJCM6J/S1eF7Vt+Uk=
|
||||
github.com/ipostelnik/cli/v2 v2.3.1-0.20210223162147-c8b4f97735e4/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d h1:PRDnysJ9dF1vUMmEzBu6aHQeUluSQy4eWH3RsSSy/vI=
|
||||
github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
@@ -544,14 +545,14 @@ github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
|
||||
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
|
||||
github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4 h1:S9YlS71UNJIyS61OqGAmLXv3w5zclSidN+qwr80XxKs=
|
||||
github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
|
||||
+2
-1
@@ -3,10 +3,11 @@ package h2mux
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"github.com/rs/zerolog"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
)
|
||||
|
||||
+3
-3
@@ -9,12 +9,12 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
gws "github.com/gorilla/websocket"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
"github.com/cloudflare/cloudflared/socks"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
gws "github.com/gorilla/websocket"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// OriginConnection is a way to stream to a service running on the user's origin.
|
||||
|
||||
@@ -13,14 +13,15 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/socks"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
gorillaWS "github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/proxy"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/socks"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -7,9 +7,10 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -11,10 +11,11 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
)
|
||||
|
||||
// TestEstablishConnectionResponse ensures each implementation of StreamBasedOriginProxy returns
|
||||
|
||||
@@ -3,10 +3,10 @@ package ingress
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,10 +5,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
)
|
||||
|
||||
// Ensure that the nullable config from `config` package and the
|
||||
|
||||
@@ -11,14 +11,17 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gws "github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/hello"
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
"github.com/cloudflare/cloudflared/socks"
|
||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
gws "github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// originService is something a tunnel can proxy traffic to.
|
||||
@@ -286,6 +289,7 @@ func newHTTPTransport(service originService, cfg OriginRequestConfig, log *zerol
|
||||
httpTransport.DialContext = dialContext
|
||||
}
|
||||
|
||||
http2.ConfigureTransport(&httpTransport)
|
||||
return &httpTransport, nil
|
||||
}
|
||||
|
||||
|
||||
+16
-15
@@ -1,11 +1,12 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var writeCalls int
|
||||
@@ -31,45 +32,45 @@ func TestResilientMultiWriter(t *testing.T) {
|
||||
writers []io.Writer
|
||||
}{
|
||||
{
|
||||
name: "All valid writers",
|
||||
name: "All valid writers",
|
||||
writers: []io.Writer{
|
||||
mockedWriter {
|
||||
mockedWriter{
|
||||
wantErr: false,
|
||||
},
|
||||
mockedWriter {
|
||||
mockedWriter{
|
||||
wantErr: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "All invalid writers",
|
||||
name: "All invalid writers",
|
||||
writers: []io.Writer{
|
||||
mockedWriter {
|
||||
mockedWriter{
|
||||
wantErr: true,
|
||||
},
|
||||
mockedWriter {
|
||||
mockedWriter{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "First invalid writer",
|
||||
name: "First invalid writer",
|
||||
writers: []io.Writer{
|
||||
mockedWriter {
|
||||
mockedWriter{
|
||||
wantErr: true,
|
||||
},
|
||||
mockedWriter {
|
||||
mockedWriter{
|
||||
wantErr: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "First valid writer",
|
||||
name: "First valid writer",
|
||||
writers: []io.Writer{
|
||||
mockedWriter {
|
||||
mockedWriter{
|
||||
wantErr: false,
|
||||
},
|
||||
mockedWriter {
|
||||
mockedWriter{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
@@ -86,4 +87,4 @@ func TestResilientMultiWriter(t *testing.T) {
|
||||
assert.Equal(t, len(writers), writeCalls)
|
||||
writeCalls = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-21
@@ -1,47 +1,48 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"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
|
||||
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,
|
||||
}
|
||||
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()
|
||||
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
|
||||
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))
|
||||
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)
|
||||
i.Observe(i.End(labelVal), labelVal)
|
||||
}
|
||||
|
||||
func Latency(startTime, endTime time.Time) time.Duration {
|
||||
return endTime.Sub(startTime)
|
||||
return endTime.Sub(startTime)
|
||||
}
|
||||
|
||||
+16
-16
@@ -1,24 +1,24 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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"))
|
||||
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"))
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
package origin
|
||||
|
||||
import (
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
)
|
||||
|
||||
// Metrics uses connection.MetricsNamespace(aka cloudflared) as namespace and connection.TunnelSubsystem
|
||||
|
||||
@@ -13,20 +13,19 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
gorillaWS "github.com/gorilla/websocket"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/hello"
|
||||
"github.com/cloudflare/cloudflared/ingress"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
gorillaWS "github.com/gorilla/websocket"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
+2
-1
@@ -7,8 +7,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -7,14 +7,14 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/edgediscovery"
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/signal"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
)
|
||||
|
||||
type dynamicMockFetcher struct {
|
||||
|
||||
@@ -6,8 +6,9 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
)
|
||||
|
||||
// RequestHandler is the functions needed to handle a SOCKS5 command
|
||||
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cloudflare/cloudflared/ipaccess"
|
||||
)
|
||||
|
||||
func TestUnsupportedBind(t *testing.T) {
|
||||
|
||||
+4
-2
@@ -12,12 +12,14 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/jose"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
cfpath "github.com/cloudflare/cloudflared/token"
|
||||
)
|
||||
@@ -50,8 +52,8 @@ type errorResponse struct {
|
||||
var mockRequest func(url, contentType string, body io.Reader) (*http.Response, error) = nil
|
||||
|
||||
// GenerateShortLivedCertificate generates and stores a keypair for short lived certs
|
||||
func GenerateShortLivedCertificate(appInfo *cfpath.AppInfo, token string) error {
|
||||
fullName, err := cfpath.GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName)
|
||||
func GenerateShortLivedCertificate(appURL *url.URL, token string) error {
|
||||
fullName, err := cfpath.GenerateSSHCertFilePathFromURL(appURL, keyName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -32,11 +34,12 @@ type signingArguments struct {
|
||||
}
|
||||
|
||||
func TestCertGenSuccess(t *testing.T) {
|
||||
appInfo := &cfpath.AppInfo{AppAUD: "abcd1234", AppDomain: "mySite.com"}
|
||||
url, _ := url.Parse("https://cf-test-access.com/testpath")
|
||||
token := tokenGenerator()
|
||||
|
||||
fullName, err := cfpath.GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName)
|
||||
fullName, err := cfpath.GenerateSSHCertFilePathFromURL(url, keyName)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, strings.HasSuffix(fullName, "/cf-test-access.com-testpath-cf_key"))
|
||||
|
||||
pubKeyName := fullName + ".pub"
|
||||
certKeyName := fullName + "-cert.pub"
|
||||
@@ -65,7 +68,7 @@ func TestCertGenSuccess(t *testing.T) {
|
||||
return w.Result(), nil
|
||||
}
|
||||
|
||||
err = GenerateShortLivedCertificate(appInfo, token)
|
||||
err = GenerateShortLivedCertificate(url, token)
|
||||
assert.NoError(t, err)
|
||||
|
||||
exist, err := config.FileExists(fullName)
|
||||
|
||||
+12
-1
@@ -2,15 +2,26 @@ package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
)
|
||||
|
||||
// GenerateSSHCertFilePathFromURL will return a file path for creating short lived certificates
|
||||
func GenerateSSHCertFilePathFromURL(url *url.URL, suffix string) (string, error) {
|
||||
configPath, err := getConfigPath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := strings.Replace(fmt.Sprintf("%s%s-%s", url.Hostname(), url.EscapedPath(), suffix), "/", "-", -1)
|
||||
return filepath.Join(configPath, name), nil
|
||||
}
|
||||
|
||||
// GenerateAppTokenFilePathFromURL will return a filepath for given Access org token
|
||||
func GenerateAppTokenFilePathFromURL(appDomain, aud string, suffix string) (string, error) {
|
||||
configPath, err := getConfigPath()
|
||||
|
||||
@@ -4,4 +4,3 @@ package token
|
||||
func OpenBrowser(url string) error {
|
||||
return getBrowserCmd(url).Start()
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -13,12 +13,12 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/origin"
|
||||
|
||||
"github.com/coreos/go-oidc/jose"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/origin"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+8
-8
@@ -17,7 +17,7 @@ func TestSignalHandler(t *testing.T) {
|
||||
handlerRan := false
|
||||
done := make(chan struct{})
|
||||
timer := time.NewTimer(time.Second)
|
||||
sigHandler.register(func(){
|
||||
sigHandler.register(func() {
|
||||
handlerRan = true
|
||||
done <- struct{}{}
|
||||
})
|
||||
@@ -28,10 +28,10 @@ func TestSignalHandler(t *testing.T) {
|
||||
|
||||
// Blocks for up to one second to make sure the handler callback runs before the assert.
|
||||
select {
|
||||
case <- done:
|
||||
assert.True(t, handlerRan)
|
||||
case <- timer.C:
|
||||
t.Fail()
|
||||
case <-done:
|
||||
assert.True(t, handlerRan)
|
||||
case <-timer.C:
|
||||
t.Fail()
|
||||
}
|
||||
sigHandler.deregister()
|
||||
}
|
||||
@@ -40,15 +40,15 @@ func TestSignalHandlerClose(t *testing.T) {
|
||||
sigHandler := signalHandler{signals: []os.Signal{syscall.SIGUSR1}}
|
||||
done := make(chan struct{})
|
||||
timer := time.NewTimer(time.Second)
|
||||
sigHandler.register(func(){done <- struct{}{}})
|
||||
sigHandler.register(func() { done <- struct{}{} })
|
||||
sigHandler.deregister()
|
||||
|
||||
p, err := os.FindProcess(os.Getpid())
|
||||
require.Nil(t, err)
|
||||
p.Signal(syscall.SIGUSR1)
|
||||
select {
|
||||
case <- done:
|
||||
case <-done:
|
||||
t.Fail()
|
||||
case <- timer.C:
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,4 +129,4 @@ func (ao AuthUnknown) isAuthOutcome() {}
|
||||
|
||||
func hoursToTime(hours uint8) time.Duration {
|
||||
return time.Duration(hours) * time.Hour
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package pogs
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
|
||||
"zombiezen.com/go/capnproto2/pogs"
|
||||
"zombiezen.com/go/capnproto2/server"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
)
|
||||
|
||||
func (i TunnelServer_PogsImpl) Authenticate(p tunnelrpc.TunnelServer_authenticate) error {
|
||||
|
||||
@@ -6,9 +6,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
)
|
||||
|
||||
// Ensure the AuthOutcome sum is correct
|
||||
|
||||
@@ -3,8 +3,9 @@ package pogs
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"zombiezen.com/go/capnproto2/server"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
)
|
||||
|
||||
func (i TunnelServer_PogsImpl) ReconnectTunnel(p tunnelrpc.TunnelServer_reconnectTunnel) error {
|
||||
|
||||
@@ -38,4 +38,3 @@ func (mockTunnelServerBase) Authenticate(ctx context.Context, originCert []byte,
|
||||
func (mockTunnelServerBase) ReconnectTunnel(ctx context.Context, jwt, eventDigest, connDigest []byte, hostname string, options *RegistrationOptions) (*TunnelRegistration, error) {
|
||||
panic("unexpected call to ReconnectTunnel")
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
"zombiezen.com/go/capnproto2/pogs"
|
||||
"zombiezen.com/go/capnproto2/rpc"
|
||||
"zombiezen.com/go/capnproto2/server"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
capnp "zombiezen.com/go/capnproto2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+11
-6
@@ -12,10 +12,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/teamnet"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/teamnet"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -246,6 +249,11 @@ func NewRESTClient(baseURL, accountTag, zoneTag, authToken, userAgent string, lo
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create account level endpoint")
|
||||
}
|
||||
httpTransport := http.Transport{
|
||||
TLSHandshakeTimeout: defaultTimeout,
|
||||
ResponseHeaderTimeout: defaultTimeout,
|
||||
}
|
||||
http2.ConfigureTransport(&httpTransport)
|
||||
return &RESTClient{
|
||||
baseEndpoints: &baseEndpoints{
|
||||
accountLevel: *accountLevelEndpoint,
|
||||
@@ -255,11 +263,8 @@ func NewRESTClient(baseURL, accountTag, zoneTag, authToken, userAgent string, lo
|
||||
authToken: authToken,
|
||||
userAgent: userAgent,
|
||||
client: http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSHandshakeTimeout: defaultTimeout,
|
||||
ResponseHeaderTimeout: defaultTimeout,
|
||||
},
|
||||
Timeout: defaultTimeout,
|
||||
Transport: &httpTransport,
|
||||
Timeout: defaultTimeout,
|
||||
},
|
||||
log: log,
|
||||
}, nil
|
||||
|
||||
@@ -7,8 +7,9 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/cloudflare/cloudflared/teamnet"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cloudflare/cloudflared/teamnet"
|
||||
)
|
||||
|
||||
// ListRoutes calls the Tunnelstore GET endpoint for all routes under an account.
|
||||
|
||||
@@ -4,12 +4,11 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/idna"
|
||||
"gopkg.in/coreos/go-oidc.v2"
|
||||
|
||||
+67
-23
@@ -1,4 +1,6 @@
|
||||
Blackfriday [](https://travis-ci.org/russross/blackfriday)
|
||||
Blackfriday
|
||||
[![Build Status][BuildV2SVG]][BuildV2URL]
|
||||
[![PkgGoDev][PkgGoDevV2SVG]][PkgGoDevV2URL]
|
||||
===========
|
||||
|
||||
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
|
||||
@@ -16,19 +18,21 @@ It started as a translation from C of [Sundown][3].
|
||||
Installation
|
||||
------------
|
||||
|
||||
Blackfriday is compatible with any modern Go release. With Go 1.7 and git
|
||||
installed:
|
||||
Blackfriday is compatible with modern Go releases in module mode.
|
||||
With Go installed:
|
||||
|
||||
go get gopkg.in/russross/blackfriday.v2
|
||||
go get github.com/russross/blackfriday/v2
|
||||
|
||||
will download, compile, and install the package into your `$GOPATH`
|
||||
directory hierarchy. Alternatively, you can achieve the same if you
|
||||
import it into a project:
|
||||
will resolve and add the package to the current development module,
|
||||
then build and install it. Alternatively, you can achieve the same
|
||||
if you import it in a package:
|
||||
|
||||
import "gopkg.in/russross/blackfriday.v2"
|
||||
import "github.com/russross/blackfriday/v2"
|
||||
|
||||
and `go get` without parameters.
|
||||
|
||||
Legacy GOPATH mode is unsupported.
|
||||
|
||||
|
||||
Versions
|
||||
--------
|
||||
@@ -36,13 +40,9 @@ Versions
|
||||
Currently maintained and recommended version of Blackfriday is `v2`. It's being
|
||||
developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
|
||||
documentation is available at
|
||||
https://godoc.org/gopkg.in/russross/blackfriday.v2.
|
||||
https://pkg.go.dev/github.com/russross/blackfriday/v2.
|
||||
|
||||
It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`,
|
||||
but we highly recommend using package management tool like [dep][7] or
|
||||
[Glide][8] and make use of semantic versioning. With package management you
|
||||
should import `github.com/russross/blackfriday` and specify that you're using
|
||||
version 2.0.0.
|
||||
It is `go get`-able in module mode at `github.com/russross/blackfriday/v2`.
|
||||
|
||||
Version 2 offers a number of improvements over v1:
|
||||
|
||||
@@ -62,6 +62,11 @@ Potential drawbacks:
|
||||
v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
|
||||
tracking.
|
||||
|
||||
If you are still interested in the legacy `v1`, you can import it from
|
||||
`github.com/russross/blackfriday`. Documentation for the legacy v1 can be found
|
||||
here: https://pkg.go.dev/github.com/russross/blackfriday.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
@@ -91,7 +96,7 @@ Here's an example of simple usage of Blackfriday together with Bluemonday:
|
||||
```go
|
||||
import (
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday"
|
||||
"github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
// ...
|
||||
@@ -104,6 +109,8 @@ html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
||||
If you want to customize the set of options, use `blackfriday.WithExtensions`,
|
||||
`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
|
||||
|
||||
### `blackfriday-tool`
|
||||
|
||||
You can also check out `blackfriday-tool` for a more complete example
|
||||
of how to use it. Download and install it using:
|
||||
|
||||
@@ -114,7 +121,7 @@ markdown file using a standalone program. You can also browse the
|
||||
source directly on github if you are just looking for some example
|
||||
code:
|
||||
|
||||
* <http://github.com/russross/blackfriday-tool>
|
||||
* <https://github.com/russross/blackfriday-tool>
|
||||
|
||||
Note that if you have not already done so, installing
|
||||
`blackfriday-tool` will be sufficient to download and install
|
||||
@@ -123,6 +130,22 @@ installed in `$GOPATH/bin`. This is a statically-linked binary that
|
||||
can be copied to wherever you need it without worrying about
|
||||
dependencies and library versions.
|
||||
|
||||
### Sanitized anchor names
|
||||
|
||||
Blackfriday includes an algorithm for creating sanitized anchor names
|
||||
corresponding to a given input text. This algorithm is used to create
|
||||
anchors for headings when `AutoHeadingIDs` extension is enabled. The
|
||||
algorithm has a specification, so that other packages can create
|
||||
compatible anchor names and links to those anchors.
|
||||
|
||||
The specification is located at https://pkg.go.dev/github.com/russross/blackfriday/v2#hdr-Sanitized_Anchor_Names.
|
||||
|
||||
[`SanitizedAnchorName`](https://pkg.go.dev/github.com/russross/blackfriday/v2#SanitizedAnchorName) exposes this functionality, and can be used to
|
||||
create compatible links to the anchor names generated by blackfriday.
|
||||
This algorithm is also implemented in a small standalone package at
|
||||
[`github.com/shurcooL/sanitized_anchor_name`](https://pkg.go.dev/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients
|
||||
that want a small package and don't need full functionality of blackfriday.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
@@ -199,6 +222,15 @@ implements the following extensions:
|
||||
You can use 3 or more backticks to mark the beginning of the
|
||||
block, and the same number to mark the end of the block.
|
||||
|
||||
To preserve classes of fenced code blocks while using the bluemonday
|
||||
HTML sanitizer, use the following policy:
|
||||
|
||||
```go
|
||||
p := bluemonday.UGCPolicy()
|
||||
p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
|
||||
html := p.SanitizeBytes(unsafe)
|
||||
```
|
||||
|
||||
* **Definition lists**. A simple definition list is made of a single-line
|
||||
term followed by a colon and the definition for that term.
|
||||
|
||||
@@ -250,7 +282,7 @@ Other renderers
|
||||
Blackfriday is structured to allow alternative rendering engines. Here
|
||||
are a few of note:
|
||||
|
||||
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
|
||||
* [github_flavored_markdown](https://pkg.go.dev/github.com/shurcooL/github_flavored_markdown):
|
||||
provides a GitHub Flavored Markdown renderer with fenced code block
|
||||
highlighting, clickable heading anchor links.
|
||||
|
||||
@@ -261,20 +293,28 @@ are a few of note:
|
||||
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
|
||||
but for markdown.
|
||||
|
||||
* [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX):
|
||||
* [LaTeX output](https://gitlab.com/ambrevar/blackfriday-latex):
|
||||
renders output as LaTeX.
|
||||
|
||||
* [bfchroma](https://github.com/Depado/bfchroma/): provides convenience
|
||||
integration with the [Chroma](https://github.com/alecthomas/chroma) code
|
||||
highlighting library. bfchroma is only compatible with v2 of Blackfriday and
|
||||
provides a drop-in renderer ready to use with Blackfriday, as well as
|
||||
options and means for further customization.
|
||||
|
||||
* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer.
|
||||
|
||||
* [Blackfriday-Slack](https://github.com/karriereat/blackfriday-slack): converts markdown to slack message style
|
||||
|
||||
Todo
|
||||
|
||||
TODO
|
||||
----
|
||||
|
||||
* More unit testing
|
||||
* Improve unicode support. It does not understand all unicode
|
||||
* Improve Unicode support. It does not understand all Unicode
|
||||
rules (about what constitutes a letter, a punctuation symbol,
|
||||
etc.), so it may fail to detect word boundaries correctly in
|
||||
some instances. It is safe on all utf-8 input.
|
||||
some instances. It is safe on all UTF-8 input.
|
||||
|
||||
|
||||
License
|
||||
@@ -286,6 +326,10 @@ License
|
||||
[1]: https://daringfireball.net/projects/markdown/ "Markdown"
|
||||
[2]: https://golang.org/ "Go Language"
|
||||
[3]: https://github.com/vmg/sundown "Sundown"
|
||||
[4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func"
|
||||
[4]: https://pkg.go.dev/github.com/russross/blackfriday/v2#Parse "Parse func"
|
||||
[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
|
||||
[6]: https://labix.org/gopkg.in "gopkg.in"
|
||||
|
||||
[BuildV2SVG]: https://travis-ci.org/russross/blackfriday.svg?branch=v2
|
||||
[BuildV2URL]: https://travis-ci.org/russross/blackfriday
|
||||
[PkgGoDevV2SVG]: https://pkg.go.dev/badge/github.com/russross/blackfriday/v2
|
||||
[PkgGoDevV2URL]: https://pkg.go.dev/github.com/russross/blackfriday/v2
|
||||
|
||||
+26
-4
@@ -18,8 +18,7 @@ import (
|
||||
"html"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/shurcooL/sanitized_anchor_name"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -259,7 +258,7 @@ func (p *Markdown) prefixHeading(data []byte) int {
|
||||
}
|
||||
if end > i {
|
||||
if id == "" && p.extensions&AutoHeadingIDs != 0 {
|
||||
id = sanitized_anchor_name.Create(string(data[i:end]))
|
||||
id = SanitizedAnchorName(string(data[i:end]))
|
||||
}
|
||||
block := p.addBlock(Heading, data[i:end])
|
||||
block.HeadingID = id
|
||||
@@ -673,6 +672,7 @@ func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
|
||||
if beg == 0 || beg >= len(data) {
|
||||
return 0
|
||||
}
|
||||
fenceLength := beg - 1
|
||||
|
||||
var work bytes.Buffer
|
||||
work.Write([]byte(info))
|
||||
@@ -706,6 +706,7 @@ func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
|
||||
if doRender {
|
||||
block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
|
||||
block.IsFenced = true
|
||||
block.FenceLength = fenceLength
|
||||
finalizeCodeBlock(block)
|
||||
}
|
||||
|
||||
@@ -1503,7 +1504,7 @@ func (p *Markdown) paragraph(data []byte) int {
|
||||
|
||||
id := ""
|
||||
if p.extensions&AutoHeadingIDs != 0 {
|
||||
id = sanitized_anchor_name.Create(string(data[prev:eol]))
|
||||
id = SanitizedAnchorName(string(data[prev:eol]))
|
||||
}
|
||||
|
||||
block := p.addBlock(Heading, data[prev:eol])
|
||||
@@ -1588,3 +1589,24 @@ func skipUntilChar(text []byte, start int, char byte) int {
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// SanitizedAnchorName returns a sanitized anchor name for the given text.
|
||||
//
|
||||
// It implements the algorithm specified in the package comment.
|
||||
func SanitizedAnchorName(text string) string {
|
||||
var anchorName []rune
|
||||
futureDash := false
|
||||
for _, r := range text {
|
||||
switch {
|
||||
case unicode.IsLetter(r) || unicode.IsNumber(r):
|
||||
if futureDash && len(anchorName) > 0 {
|
||||
anchorName = append(anchorName, '-')
|
||||
}
|
||||
futureDash = false
|
||||
anchorName = append(anchorName, unicode.ToLower(r))
|
||||
default:
|
||||
futureDash = true
|
||||
}
|
||||
}
|
||||
return string(anchorName)
|
||||
}
|
||||
|
||||
+28
@@ -15,4 +15,32 @@
|
||||
//
|
||||
// If you're interested in calling Blackfriday from command line, see
|
||||
// https://github.com/russross/blackfriday-tool.
|
||||
//
|
||||
// Sanitized Anchor Names
|
||||
//
|
||||
// Blackfriday includes an algorithm for creating sanitized anchor names
|
||||
// corresponding to a given input text. This algorithm is used to create
|
||||
// anchors for headings when AutoHeadingIDs extension is enabled. The
|
||||
// algorithm is specified below, so that other packages can create
|
||||
// compatible anchor names and links to those anchors.
|
||||
//
|
||||
// The algorithm iterates over the input text, interpreted as UTF-8,
|
||||
// one Unicode code point (rune) at a time. All runes that are letters (category L)
|
||||
// or numbers (category N) are considered valid characters. They are mapped to
|
||||
// lower case, and included in the output. All other runes are considered
|
||||
// invalid characters. Invalid characters that precede the first valid character,
|
||||
// as well as invalid character that follow the last valid character
|
||||
// are dropped completely. All other sequences of invalid characters
|
||||
// between two valid characters are replaced with a single dash character '-'.
|
||||
//
|
||||
// SanitizedAnchorName exposes this functionality, and can be used to
|
||||
// create compatible links to the anchor names generated by blackfriday.
|
||||
// This algorithm is also implemented in a small standalone package at
|
||||
// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients
|
||||
// that want a small package and don't need full functionality of blackfriday.
|
||||
package blackfriday
|
||||
|
||||
// NOTE: Keep Sanitized Anchor Name algorithm in sync with package
|
||||
// github.com/shurcooL/sanitized_anchor_name.
|
||||
// Otherwise, users of sanitized_anchor_name will get anchor names
|
||||
// that are incompatible with those generated by blackfriday.
|
||||
|
||||
+2236
File diff suppressed because it is too large
Load Diff
+39
-3
@@ -13,13 +13,27 @@ var htmlEscaper = [256][]byte{
|
||||
}
|
||||
|
||||
func escapeHTML(w io.Writer, s []byte) {
|
||||
escapeEntities(w, s, false)
|
||||
}
|
||||
|
||||
func escapeAllHTML(w io.Writer, s []byte) {
|
||||
escapeEntities(w, s, true)
|
||||
}
|
||||
|
||||
func escapeEntities(w io.Writer, s []byte, escapeValidEntities bool) {
|
||||
var start, end int
|
||||
for end < len(s) {
|
||||
escSeq := htmlEscaper[s[end]]
|
||||
if escSeq != nil {
|
||||
w.Write(s[start:end])
|
||||
w.Write(escSeq)
|
||||
start = end + 1
|
||||
isEntity, entityEnd := nodeIsEntity(s, end)
|
||||
if isEntity && !escapeValidEntities {
|
||||
w.Write(s[start : entityEnd+1])
|
||||
start = entityEnd + 1
|
||||
} else {
|
||||
w.Write(s[start:end])
|
||||
w.Write(escSeq)
|
||||
start = end + 1
|
||||
}
|
||||
}
|
||||
end++
|
||||
}
|
||||
@@ -28,6 +42,28 @@ func escapeHTML(w io.Writer, s []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func nodeIsEntity(s []byte, end int) (isEntity bool, endEntityPos int) {
|
||||
isEntity = false
|
||||
endEntityPos = end + 1
|
||||
|
||||
if s[end] == '&' {
|
||||
for endEntityPos < len(s) {
|
||||
if s[endEntityPos] == ';' {
|
||||
if entities[string(s[end:endEntityPos+1])] {
|
||||
isEntity = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isalnum(s[endEntityPos]) && s[endEntityPos] != '&' && s[endEntityPos] != '#' {
|
||||
break
|
||||
}
|
||||
endEntityPos++
|
||||
}
|
||||
}
|
||||
|
||||
return isEntity, endEntityPos
|
||||
}
|
||||
|
||||
func escLink(w io.Writer, text []byte) {
|
||||
unesc := html.UnescapeString(string(text))
|
||||
escapeHTML(w, []byte(unesc))
|
||||
|
||||
+6
-3
@@ -132,7 +132,10 @@ func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
|
||||
}
|
||||
|
||||
if params.FootnoteReturnLinkContents == "" {
|
||||
params.FootnoteReturnLinkContents = `<sup>[return]</sup>`
|
||||
// U+FE0E is VARIATION SELECTOR-15.
|
||||
// It suppresses automatic emoji presentation of the preceding
|
||||
// U+21A9 LEFTWARDS ARROW WITH HOOK on iOS and iPadOS.
|
||||
params.FootnoteReturnLinkContents = "<span aria-label='Return'>↩\ufe0e</span>"
|
||||
}
|
||||
|
||||
return &HTMLRenderer{
|
||||
@@ -616,7 +619,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
|
||||
}
|
||||
case Code:
|
||||
r.out(w, codeTag)
|
||||
escapeHTML(w, node.Literal)
|
||||
escapeAllHTML(w, node.Literal)
|
||||
r.out(w, codeCloseTag)
|
||||
case Document:
|
||||
break
|
||||
@@ -762,7 +765,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
|
||||
r.cr(w)
|
||||
r.out(w, preTag)
|
||||
r.tag(w, codeTag[:len(codeTag)-1], attrs)
|
||||
escapeHTML(w, node.Literal)
|
||||
escapeAllHTML(w, node.Literal)
|
||||
r.out(w, codeCloseTag)
|
||||
r.out(w, preCloseTag)
|
||||
if node.Parent.Type != Item {
|
||||
|
||||
+1
-1
@@ -278,7 +278,7 @@ func link(p *Markdown, data []byte, offset int) (int, *Node) {
|
||||
case data[i] == '\n':
|
||||
textHasNl = true
|
||||
|
||||
case data[i-1] == '\\':
|
||||
case isBackslashEscaped(data, i):
|
||||
continue
|
||||
|
||||
case data[i] == '[':
|
||||
|
||||
+9
-3
@@ -199,7 +199,8 @@ func (n *Node) InsertBefore(sibling *Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) isContainer() bool {
|
||||
// IsContainer returns true if 'n' can contain children.
|
||||
func (n *Node) IsContainer() bool {
|
||||
switch n.Type {
|
||||
case Document:
|
||||
fallthrough
|
||||
@@ -238,6 +239,11 @@ func (n *Node) isContainer() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// IsLeaf returns true if 'n' is a leaf node.
|
||||
func (n *Node) IsLeaf() bool {
|
||||
return !n.IsContainer()
|
||||
}
|
||||
|
||||
func (n *Node) canContain(t NodeType) bool {
|
||||
if n.Type == List {
|
||||
return t == Item
|
||||
@@ -309,11 +315,11 @@ func newNodeWalker(root *Node) *nodeWalker {
|
||||
}
|
||||
|
||||
func (nw *nodeWalker) next() {
|
||||
if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root {
|
||||
if (!nw.current.IsContainer() || !nw.entering) && nw.current == nw.root {
|
||||
nw.current = nil
|
||||
return
|
||||
}
|
||||
if nw.entering && nw.current.isContainer() {
|
||||
if nw.entering && nw.current.IsContainer() {
|
||||
if nw.current.FirstChild != nil {
|
||||
nw.current = nw.current.FirstChild
|
||||
nw.entering = true
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.x
|
||||
- master
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
fast_finish: true
|
||||
install:
|
||||
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||
- go tool vet .
|
||||
- go test -v -race ./...
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user