mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
[新增] Agent 架构调整, 采用集成镜像方式
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
.git
|
||||||
|
.idea
|
||||||
|
anubis-source
|
||||||
|
**/node_modules
|
||||||
|
**/.next
|
||||||
|
**/build
|
||||||
|
**/dist
|
||||||
|
**/.cache
|
||||||
|
**/coverage
|
||||||
|
**/*.db
|
||||||
|
**/*.log
|
||||||
@@ -179,3 +179,166 @@ jobs:
|
|||||||
|
|
||||||
- name: Inspect image
|
- name: Inspect image
|
||||||
run: docker buildx imagetools inspect "${IMAGE}:${VERSION}"
|
run: docker buildx imagetools inspect "${IMAGE}:${VERSION}"
|
||||||
|
|
||||||
|
build-agent:
|
||||||
|
name: Build Agent (${{ matrix.arch }})
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: amd64
|
||||||
|
platform: linux/amd64
|
||||||
|
runner: ubuntu-24.04
|
||||||
|
- arch: arm64
|
||||||
|
platform: linux/arm64
|
||||||
|
runner: ubuntu-24.04-arm
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-tags: true
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Set image metadata
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
INPUT_VERSION: ${{ github.event.inputs.version }}
|
||||||
|
run: |
|
||||||
|
POINTED_TAG="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)"
|
||||||
|
INPUT_VERSION="${INPUT_VERSION//[[:space:]]/}"
|
||||||
|
|
||||||
|
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}-agent" >> "$GITHUB_ENV"
|
||||||
|
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
||||||
|
VERSION="${GITHUB_REF_NAME}"
|
||||||
|
elif [[ -n "$INPUT_VERSION" ]]; then
|
||||||
|
VERSION="$INPUT_VERSION"
|
||||||
|
elif [[ -n "$POINTED_TAG" ]]; then
|
||||||
|
VERSION="$POINTED_TAG"
|
||||||
|
else
|
||||||
|
echo "workflow_dispatch requires an explicit version input when HEAD is not tagged" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log into registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./openflare_agent/Dockerfile
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||||
|
build-args: |
|
||||||
|
VERSION=${{ env.VERSION }}
|
||||||
|
cache-from: type=gha,scope=docker-agent-${{ matrix.arch }}
|
||||||
|
cache-to: type=gha,mode=max,scope=docker-agent-${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Export digest
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/agent-digests
|
||||||
|
touch "/tmp/agent-digests/${DIGEST#sha256:}"
|
||||||
|
env:
|
||||||
|
DIGEST: ${{ steps.build.outputs.digest }}
|
||||||
|
|
||||||
|
- name: Upload digest
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: agent-digests-${{ matrix.arch }}
|
||||||
|
path: /tmp/agent-digests/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
- name: Generate artifact attestation
|
||||||
|
uses: actions/attest-build-provenance@v3
|
||||||
|
with:
|
||||||
|
subject-name: ${{ env.IMAGE }}
|
||||||
|
subject-digest: ${{ steps.build.outputs.digest }}
|
||||||
|
push-to-registry: true
|
||||||
|
|
||||||
|
merge-agent:
|
||||||
|
name: Merge Agent multi-arch manifest
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs: build-agent
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-tags: true
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Set image metadata
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
INPUT_VERSION: ${{ github.event.inputs.version }}
|
||||||
|
run: |
|
||||||
|
POINTED_TAG="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)"
|
||||||
|
INPUT_VERSION="${INPUT_VERSION//[[:space:]]/}"
|
||||||
|
|
||||||
|
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}-agent" >> "$GITHUB_ENV"
|
||||||
|
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
||||||
|
VERSION="${GITHUB_REF_NAME}"
|
||||||
|
elif [[ -n "$INPUT_VERSION" ]]; then
|
||||||
|
VERSION="$INPUT_VERSION"
|
||||||
|
elif [[ -n "$POINTED_TAG" ]]; then
|
||||||
|
VERSION="$POINTED_TAG"
|
||||||
|
else
|
||||||
|
echo "workflow_dispatch requires an explicit version input when HEAD is not tagged" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: /tmp/agent-digests
|
||||||
|
pattern: agent-digests-*
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log into registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create and push manifest list
|
||||||
|
working-directory: /tmp/agent-digests
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
shopt -s nullglob
|
||||||
|
references=()
|
||||||
|
for digest in *; do
|
||||||
|
references+=("${IMAGE}@sha256:${digest}")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#references[@]} -eq 0 ]; then
|
||||||
|
echo "No digests found in /tmp/agent-digests" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker buildx imagetools create \
|
||||||
|
-t "${IMAGE}:${VERSION}" \
|
||||||
|
-t "${IMAGE}:latest" \
|
||||||
|
"${references[@]}"
|
||||||
|
|
||||||
|
- name: Inspect image
|
||||||
|
run: docker buildx imagetools inspect "${IMAGE}:${VERSION}"
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
ARG VERSION=dev
|
||||||
|
|
||||||
|
FROM golang:1.25-alpine AS builder
|
||||||
|
|
||||||
|
ARG VERSION
|
||||||
|
ARG TARGETOS=linux
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=0 \
|
||||||
|
GOOS=${TARGETOS} \
|
||||||
|
GOARCH=${TARGETARCH}
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
COPY openflare_server ./openflare_server
|
||||||
|
COPY openflare_agent ./openflare_agent
|
||||||
|
WORKDIR /build/openflare_agent
|
||||||
|
RUN go mod download
|
||||||
|
RUN go build -trimpath -ldflags "-s -w -X 'openflare-agent/internal/config.AgentVersion=$VERSION'" -o /build/openflare-agent ./cmd/agent
|
||||||
|
|
||||||
|
FROM openresty/openresty:alpine
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata \
|
||||||
|
&& mkdir -p /etc/openflare /data
|
||||||
|
|
||||||
|
ENV OPENFLARE_OPENRESTY_PATH=openresty \
|
||||||
|
OPENFLARE_DATA_DIR=/data
|
||||||
|
|
||||||
|
COPY --from=builder /build/openflare-agent /usr/local/bin/openflare-agent
|
||||||
|
|
||||||
|
EXPOSE 80 443 18081
|
||||||
|
ENTRYPOINT ["/usr/local/bin/openflare-agent"]
|
||||||
|
CMD ["-config", "/etc/openflare/agent.json"]
|
||||||
@@ -2,8 +2,7 @@
|
|||||||
"server_url": "http://127.0.0.1:3000",
|
"server_url": "http://127.0.0.1:3000",
|
||||||
"agent_token": "373956188ddead1df6dd7c86cd330b73",
|
"agent_token": "373956188ddead1df6dd7c86cd330b73",
|
||||||
"data_dir": "./data",
|
"data_dir": "./data",
|
||||||
"openresty_container_name": "openflare-openresty",
|
"openresty_path": "openresty",
|
||||||
"openresty_docker_image": "openresty/openresty:alpine",
|
|
||||||
"heartbeat_interval": 10000,
|
"heartbeat_interval": 10000,
|
||||||
"request_timeout": 10000
|
"request_timeout": 10000
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ func main() {
|
|||||||
context.Background(),
|
context.Background(),
|
||||||
nginx.ExecutorOptions{
|
nginx.ExecutorOptions{
|
||||||
NginxPath: cfg.OpenrestyPath,
|
NginxPath: cfg.OpenrestyPath,
|
||||||
DockerBinary: cfg.DockerBinary,
|
|
||||||
ContainerName: cfg.OpenrestyContainerName,
|
|
||||||
Image: cfg.OpenrestyDockerImage,
|
|
||||||
MainConfigPath: cfg.MainConfigPath,
|
MainConfigPath: cfg.MainConfigPath,
|
||||||
RouteConfigPath: cfg.RouteConfigPath,
|
RouteConfigPath: cfg.RouteConfigPath,
|
||||||
CertDir: cfg.CertDir,
|
CertDir: cfg.CertDir,
|
||||||
@@ -52,33 +49,29 @@ func main() {
|
|||||||
"ip", cfg.NodeIP,
|
"ip", cfg.NodeIP,
|
||||||
"heartbeat_interval", cfg.HeartbeatInterval,
|
"heartbeat_interval", cfg.HeartbeatInterval,
|
||||||
"route_config", cfg.RouteConfigPath,
|
"route_config", cfg.RouteConfigPath,
|
||||||
|
"access_log", cfg.AccessLogPath,
|
||||||
"cert_dir", cfg.CertDir,
|
"cert_dir", cfg.CertDir,
|
||||||
"lua_dir", cfg.LuaDir,
|
"lua_dir", cfg.LuaDir,
|
||||||
|
"runtime_config_dir", cfg.RuntimeConfigDir,
|
||||||
)
|
)
|
||||||
|
|
||||||
client := httpclient.New(cfg.ServerURL, cfg.InitialAuthToken(), cfg.RequestTimeout.Duration())
|
client := httpclient.New(cfg.ServerURL, cfg.InitialAuthToken(), cfg.RequestTimeout.Duration())
|
||||||
stateStore := state.NewStore(cfg.StatePath)
|
stateStore := state.NewStore(cfg.StatePath)
|
||||||
observabilityBuffer := state.NewObservabilityBufferStore(cfg.ObservabilityBufferPath)
|
observabilityBuffer := state.NewObservabilityBufferStore(cfg.ObservabilityBufferPath)
|
||||||
runtimeRouteConfigPath := cfg.RouteConfigPath
|
|
||||||
if cfg.OpenrestyPath == "" {
|
|
||||||
runtimeRouteConfigPath = nginx.DockerRouteConfigPath
|
|
||||||
}
|
|
||||||
runtimeManager := &nginx.Manager{
|
runtimeManager := &nginx.Manager{
|
||||||
MainConfigPath: cfg.MainConfigPath,
|
MainConfigPath: cfg.MainConfigPath,
|
||||||
RouteConfigPath: cfg.RouteConfigPath,
|
RouteConfigPath: cfg.RouteConfigPath,
|
||||||
RuntimeRouteConfigPath: runtimeRouteConfigPath,
|
AccessLogPath: cfg.AccessLogPath,
|
||||||
CertDir: cfg.CertDir,
|
CertDir: cfg.CertDir,
|
||||||
NginxCertDir: cfg.OpenrestyCertDir,
|
NginxCertDir: cfg.OpenrestyCertDir,
|
||||||
LuaDir: cfg.LuaDir,
|
LuaDir: cfg.LuaDir,
|
||||||
NginxLuaDir: cfg.OpenrestyLuaDir,
|
NginxLuaDir: cfg.OpenrestyLuaDir,
|
||||||
|
RuntimeConfigDir: cfg.RuntimeConfigDir,
|
||||||
OpenrestyObservabilityListen: nginx.ObservabilityListenAddress(cfg.OpenrestyPath, cfg.OpenrestyObservabilityPort),
|
OpenrestyObservabilityListen: nginx.ObservabilityListenAddress(cfg.OpenrestyPath, cfg.OpenrestyObservabilityPort),
|
||||||
OpenrestyObservabilityPort: cfg.OpenrestyObservabilityPort,
|
OpenrestyObservabilityPort: cfg.OpenrestyObservabilityPort,
|
||||||
OpenrestyResolverDirective: "",
|
OpenrestyResolverDirective: "",
|
||||||
Executor: nginx.NewExecutor(nginx.ExecutorOptions{
|
Executor: nginx.NewExecutor(nginx.ExecutorOptions{
|
||||||
NginxPath: cfg.OpenrestyPath,
|
NginxPath: cfg.OpenrestyPath,
|
||||||
DockerBinary: cfg.DockerBinary,
|
|
||||||
ContainerName: cfg.OpenrestyContainerName,
|
|
||||||
Image: cfg.OpenrestyDockerImage,
|
|
||||||
MainConfigPath: cfg.MainConfigPath,
|
MainConfigPath: cfg.MainConfigPath,
|
||||||
RouteConfigPath: cfg.RouteConfigPath,
|
RouteConfigPath: cfg.RouteConfigPath,
|
||||||
CertDir: cfg.CertDir,
|
CertDir: cfg.CertDir,
|
||||||
|
|||||||
@@ -302,15 +302,16 @@ func TestRunnerHeartbeatPayloadIncludesObservabilityExtensions(t *testing.T) {
|
|||||||
NginxVersion: "1.27.1.2",
|
NginxVersion: "1.27.1.2",
|
||||||
DataDir: tempDir,
|
DataDir: tempDir,
|
||||||
RouteConfigPath: filepath.Join(tempDir, "conf.d", "openflare_routes.conf"),
|
RouteConfigPath: filepath.Join(tempDir, "conf.d", "openflare_routes.conf"),
|
||||||
|
AccessLogPath: filepath.Join(tempDir, "var", "log", "openflare", "access.log"),
|
||||||
HeartbeatInterval: config.MillisecondDuration(10 * time.Millisecond),
|
HeartbeatInterval: config.MillisecondDuration(10 * time.Millisecond),
|
||||||
},
|
},
|
||||||
StateStore: stateStore,
|
StateStore: stateStore,
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(filepath.Dir(runner.Config.RouteConfigPath), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(runner.Config.AccessLogPath), 0o755); err != nil {
|
||||||
t.Fatalf("failed to prepare route config dir: %v", err)
|
t.Fatalf("failed to prepare access log dir: %v", err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(
|
if err := os.WriteFile(
|
||||||
filepath.Join(filepath.Dir(runner.Config.RouteConfigPath), "openflare_access.log"),
|
runner.Config.AccessLogPath,
|
||||||
[]byte("{\"ts\":\""+time.Now().UTC().Format(time.RFC3339)+"\",\"host\":\"edge.example.com\",\"path\":\"/\",\"remote_addr\":\"10.0.0.8\",\"status\":200}\n"),
|
[]byte("{\"ts\":\""+time.Now().UTC().Format(time.RFC3339)+"\",\"host\":\"edge.example.com\",\"path\":\"/\",\"remote_addr\":\"10.0.0.8\",\"status\":200}\n"),
|
||||||
0o644,
|
0o644,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|||||||
@@ -3,24 +3,26 @@ package config
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"openflare/utils/geoip/iputil"
|
"openflare/utils/geoip/iputil"
|
||||||
"os"
|
"os"
|
||||||
pathpkg "path"
|
pathpkg "path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultDockerMainConfigRelativePath = "etc/nginx/nginx.conf"
|
defaultMainConfigRelativePath = "etc/nginx/nginx.conf"
|
||||||
defaultDockerRouteConfigRelativePath = "etc/nginx/conf.d/openflare_routes.conf"
|
defaultRouteConfigRelativePath = "etc/nginx/conf.d/openflare_routes.conf"
|
||||||
defaultCertDirRelativePath = "etc/nginx/certs"
|
defaultCertDirRelativePath = "etc/nginx/certs"
|
||||||
defaultLuaDirRelativePath = "etc/nginx/lua"
|
defaultLuaDirRelativePath = "etc/nginx/lua"
|
||||||
defaultDockerStateRelativePath = "var/lib/openflare/agent-state.json"
|
defaultRuntimeConfigDirRelativePath = "etc/openflare"
|
||||||
|
defaultAccessLogRelativePath = "var/log/openflare/access.log"
|
||||||
|
defaultStateRelativePath = "var/lib/openflare/agent-state.json"
|
||||||
defaultObservabilityBufferRelativePath = "var/lib/openflare/observability-buffer.json"
|
defaultObservabilityBufferRelativePath = "var/lib/openflare/observability-buffer.json"
|
||||||
defaultDockerOpenRestyCertDir = "/etc/nginx/openflare-certs"
|
|
||||||
defaultDockerOpenRestyLuaDir = "/etc/nginx/openflare-lua"
|
|
||||||
defaultOpenRestyObservabilityPort = 18081
|
defaultOpenRestyObservabilityPort = 18081
|
||||||
defaultObservabilityReplayMinutes = 15
|
defaultObservabilityReplayMinutes = 15
|
||||||
)
|
)
|
||||||
@@ -35,16 +37,18 @@ type Config struct {
|
|||||||
NginxVersion string `json:"-"`
|
NginxVersion string `json:"-"`
|
||||||
OpenrestyPath string `json:"openresty_path"`
|
OpenrestyPath string `json:"openresty_path"`
|
||||||
OpenrestyResolvers []string `json:"openresty_resolvers,omitempty"`
|
OpenrestyResolvers []string `json:"openresty_resolvers,omitempty"`
|
||||||
OpenrestyContainerName string `json:"openresty_container_name"`
|
OpenrestyContainerName string `json:"openresty_container_name,omitempty"`
|
||||||
OpenrestyDockerImage string `json:"openresty_docker_image"`
|
OpenrestyDockerImage string `json:"openresty_docker_image,omitempty"`
|
||||||
DockerBinary string `json:"docker_binary"`
|
DockerBinary string `json:"docker_binary,omitempty"`
|
||||||
DataDir string `json:"data_dir"`
|
DataDir string `json:"data_dir"`
|
||||||
MainConfigPath string `json:"main_config_path"`
|
MainConfigPath string `json:"main_config_path"`
|
||||||
RouteConfigPath string `json:"route_config_path"`
|
RouteConfigPath string `json:"route_config_path"`
|
||||||
|
AccessLogPath string `json:"access_log_path"`
|
||||||
CertDir string `json:"cert_dir"`
|
CertDir string `json:"cert_dir"`
|
||||||
OpenrestyCertDir string `json:"openresty_cert_dir"`
|
OpenrestyCertDir string `json:"openresty_cert_dir"`
|
||||||
LuaDir string `json:"lua_dir"`
|
LuaDir string `json:"lua_dir"`
|
||||||
OpenrestyLuaDir string `json:"openresty_lua_dir"`
|
OpenrestyLuaDir string `json:"openresty_lua_dir"`
|
||||||
|
RuntimeConfigDir string `json:"runtime_config_dir"`
|
||||||
OpenrestyObservabilityPort int `json:"openresty_observability_port"`
|
OpenrestyObservabilityPort int `json:"openresty_observability_port"`
|
||||||
ObservabilityBufferPath string `json:"observability_buffer_path"`
|
ObservabilityBufferPath string `json:"observability_buffer_path"`
|
||||||
ObservabilityReplayMinutes int `json:"observability_replay_minutes"`
|
ObservabilityReplayMinutes int `json:"observability_replay_minutes"`
|
||||||
@@ -68,10 +72,12 @@ type configFile struct {
|
|||||||
DataDir string `json:"data_dir"`
|
DataDir string `json:"data_dir"`
|
||||||
MainConfigPath string `json:"main_config_path"`
|
MainConfigPath string `json:"main_config_path"`
|
||||||
RouteConfigPath string `json:"route_config_path"`
|
RouteConfigPath string `json:"route_config_path"`
|
||||||
|
AccessLogPath string `json:"access_log_path"`
|
||||||
CertDir string `json:"cert_dir"`
|
CertDir string `json:"cert_dir"`
|
||||||
OpenrestyCertDir string `json:"openresty_cert_dir"`
|
OpenrestyCertDir string `json:"openresty_cert_dir"`
|
||||||
LuaDir string `json:"lua_dir"`
|
LuaDir string `json:"lua_dir"`
|
||||||
OpenrestyLuaDir string `json:"openresty_lua_dir"`
|
OpenrestyLuaDir string `json:"openresty_lua_dir"`
|
||||||
|
RuntimeConfigDir string `json:"runtime_config_dir"`
|
||||||
OpenrestyObservabilityPort int `json:"openresty_observability_port"`
|
OpenrestyObservabilityPort int `json:"openresty_observability_port"`
|
||||||
ObservabilityBufferPath string `json:"observability_buffer_path"`
|
ObservabilityBufferPath string `json:"observability_buffer_path"`
|
||||||
ObservabilityReplayMinutes int `json:"observability_replay_minutes"`
|
ObservabilityReplayMinutes int `json:"observability_replay_minutes"`
|
||||||
@@ -82,11 +88,16 @@ type configFile struct {
|
|||||||
|
|
||||||
func Load(path string) (*Config, error) {
|
func Load(path string) (*Config, error) {
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
file := &configFile{}
|
file := &configFile{}
|
||||||
if err = json.Unmarshal(data, file); err != nil {
|
if err == nil {
|
||||||
|
if err = json.Unmarshal(data, file); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil && !hasEnvConfig() {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
@@ -103,10 +114,12 @@ func Load(path string) (*Config, error) {
|
|||||||
DataDir: file.DataDir,
|
DataDir: file.DataDir,
|
||||||
MainConfigPath: file.MainConfigPath,
|
MainConfigPath: file.MainConfigPath,
|
||||||
RouteConfigPath: file.RouteConfigPath,
|
RouteConfigPath: file.RouteConfigPath,
|
||||||
|
AccessLogPath: file.AccessLogPath,
|
||||||
CertDir: file.CertDir,
|
CertDir: file.CertDir,
|
||||||
OpenrestyCertDir: file.OpenrestyCertDir,
|
OpenrestyCertDir: file.OpenrestyCertDir,
|
||||||
LuaDir: file.LuaDir,
|
LuaDir: file.LuaDir,
|
||||||
OpenrestyLuaDir: file.OpenrestyLuaDir,
|
OpenrestyLuaDir: file.OpenrestyLuaDir,
|
||||||
|
RuntimeConfigDir: file.RuntimeConfigDir,
|
||||||
OpenrestyObservabilityPort: file.OpenrestyObservabilityPort,
|
OpenrestyObservabilityPort: file.OpenrestyObservabilityPort,
|
||||||
ObservabilityBufferPath: file.ObservabilityBufferPath,
|
ObservabilityBufferPath: file.ObservabilityBufferPath,
|
||||||
ObservabilityReplayMinutes: file.ObservabilityReplayMinutes,
|
ObservabilityReplayMinutes: file.ObservabilityReplayMinutes,
|
||||||
@@ -115,6 +128,7 @@ func Load(path string) (*Config, error) {
|
|||||||
RequestTimeout: file.RequestTimeout,
|
RequestTimeout: file.RequestTimeout,
|
||||||
}
|
}
|
||||||
cfg.configPath = path
|
cfg.configPath = path
|
||||||
|
applyEnvOverrides(cfg)
|
||||||
applyDefaults(cfg, filepath.Dir(path))
|
applyDefaults(cfg, filepath.Dir(path))
|
||||||
if err = validate(cfg); err != nil {
|
if err = validate(cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -126,14 +140,8 @@ func applyDefaults(cfg *Config, baseDir string) {
|
|||||||
baseDir = filepath.Clean(baseDir)
|
baseDir = filepath.Clean(baseDir)
|
||||||
cfg.AgentVersion = AgentVersion
|
cfg.AgentVersion = AgentVersion
|
||||||
cfg.OpenrestyResolvers = normalizeResolverList(cfg.OpenrestyResolvers)
|
cfg.OpenrestyResolvers = normalizeResolverList(cfg.OpenrestyResolvers)
|
||||||
if cfg.OpenrestyContainerName == "" {
|
if cfg.OpenrestyPath == "" {
|
||||||
cfg.OpenrestyContainerName = "openflare-openresty"
|
cfg.OpenrestyPath = "openresty"
|
||||||
}
|
|
||||||
if cfg.OpenrestyDockerImage == "" {
|
|
||||||
cfg.OpenrestyDockerImage = "openresty/openresty:alpine"
|
|
||||||
}
|
|
||||||
if cfg.DockerBinary == "" {
|
|
||||||
cfg.DockerBinary = "docker"
|
|
||||||
}
|
}
|
||||||
if cfg.DataDir == "" {
|
if cfg.DataDir == "" {
|
||||||
cfg.DataDir = filepath.Join(baseDir, "data")
|
cfg.DataDir = filepath.Join(baseDir, "data")
|
||||||
@@ -144,40 +152,32 @@ func applyDefaults(cfg *Config, baseDir string) {
|
|||||||
if cfg.NodeIP == "" {
|
if cfg.NodeIP == "" {
|
||||||
cfg.NodeIP = detectNodeIP()
|
cfg.NodeIP = detectNodeIP()
|
||||||
}
|
}
|
||||||
if cfg.OpenrestyPath == "" {
|
if cfg.MainConfigPath == "" {
|
||||||
cfg.MainConfigPath = joinManagedPath(cfg.DataDir, defaultDockerMainConfigRelativePath)
|
cfg.MainConfigPath = joinManagedPath(cfg.DataDir, defaultMainConfigRelativePath)
|
||||||
cfg.RouteConfigPath = joinManagedPath(cfg.DataDir, defaultDockerRouteConfigRelativePath)
|
}
|
||||||
cfg.StatePath = joinManagedPath(cfg.DataDir, defaultDockerStateRelativePath)
|
if cfg.RouteConfigPath == "" {
|
||||||
} else {
|
cfg.RouteConfigPath = joinManagedPath(cfg.DataDir, defaultRouteConfigRelativePath)
|
||||||
if cfg.MainConfigPath == "" {
|
}
|
||||||
cfg.MainConfigPath = joinManagedPath(cfg.DataDir, defaultDockerMainConfigRelativePath)
|
if cfg.AccessLogPath == "" {
|
||||||
}
|
cfg.AccessLogPath = joinManagedPath(cfg.DataDir, defaultAccessLogRelativePath)
|
||||||
if cfg.RouteConfigPath == "" {
|
}
|
||||||
cfg.RouteConfigPath = joinManagedPath(cfg.DataDir, defaultDockerRouteConfigRelativePath)
|
if cfg.StatePath == "" {
|
||||||
}
|
cfg.StatePath = joinManagedPath(cfg.DataDir, defaultStateRelativePath)
|
||||||
if cfg.StatePath == "" {
|
|
||||||
cfg.StatePath = joinManagedPath(cfg.DataDir, defaultDockerStateRelativePath)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if cfg.CertDir == "" {
|
if cfg.CertDir == "" {
|
||||||
cfg.CertDir = joinManagedPath(cfg.DataDir, defaultCertDirRelativePath)
|
cfg.CertDir = joinManagedPath(cfg.DataDir, defaultCertDirRelativePath)
|
||||||
}
|
}
|
||||||
if cfg.OpenrestyCertDir == "" {
|
if cfg.OpenrestyCertDir == "" {
|
||||||
if cfg.OpenrestyPath != "" {
|
cfg.OpenrestyCertDir = cfg.CertDir
|
||||||
cfg.OpenrestyCertDir = cfg.CertDir
|
|
||||||
} else {
|
|
||||||
cfg.OpenrestyCertDir = defaultDockerOpenRestyCertDir
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if cfg.LuaDir == "" {
|
if cfg.LuaDir == "" {
|
||||||
cfg.LuaDir = joinManagedPath(cfg.DataDir, defaultLuaDirRelativePath)
|
cfg.LuaDir = joinManagedPath(cfg.DataDir, defaultLuaDirRelativePath)
|
||||||
}
|
}
|
||||||
if cfg.OpenrestyLuaDir == "" {
|
if cfg.OpenrestyLuaDir == "" {
|
||||||
if cfg.OpenrestyPath != "" {
|
cfg.OpenrestyLuaDir = cfg.LuaDir
|
||||||
cfg.OpenrestyLuaDir = cfg.LuaDir
|
}
|
||||||
} else {
|
if cfg.RuntimeConfigDir == "" {
|
||||||
cfg.OpenrestyLuaDir = defaultDockerOpenRestyLuaDir
|
cfg.RuntimeConfigDir = joinManagedPath(cfg.DataDir, defaultRuntimeConfigDirRelativePath)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if cfg.OpenrestyObservabilityPort <= 0 {
|
if cfg.OpenrestyObservabilityPort <= 0 {
|
||||||
cfg.OpenrestyObservabilityPort = defaultOpenRestyObservabilityPort
|
cfg.OpenrestyObservabilityPort = defaultOpenRestyObservabilityPort
|
||||||
@@ -210,6 +210,9 @@ func normalizeManagedPaths(cfg *Config) {
|
|||||||
if usesSlashPath(cfg.RouteConfigPath) {
|
if usesSlashPath(cfg.RouteConfigPath) {
|
||||||
cfg.RouteConfigPath = filepath.ToSlash(cfg.RouteConfigPath)
|
cfg.RouteConfigPath = filepath.ToSlash(cfg.RouteConfigPath)
|
||||||
}
|
}
|
||||||
|
if usesSlashPath(cfg.AccessLogPath) {
|
||||||
|
cfg.AccessLogPath = filepath.ToSlash(cfg.AccessLogPath)
|
||||||
|
}
|
||||||
if usesSlashPath(cfg.CertDir) {
|
if usesSlashPath(cfg.CertDir) {
|
||||||
cfg.CertDir = filepath.ToSlash(cfg.CertDir)
|
cfg.CertDir = filepath.ToSlash(cfg.CertDir)
|
||||||
}
|
}
|
||||||
@@ -222,6 +225,9 @@ func normalizeManagedPaths(cfg *Config) {
|
|||||||
if usesSlashPath(cfg.OpenrestyLuaDir) {
|
if usesSlashPath(cfg.OpenrestyLuaDir) {
|
||||||
cfg.OpenrestyLuaDir = filepath.ToSlash(cfg.OpenrestyLuaDir)
|
cfg.OpenrestyLuaDir = filepath.ToSlash(cfg.OpenrestyLuaDir)
|
||||||
}
|
}
|
||||||
|
if usesSlashPath(cfg.RuntimeConfigDir) {
|
||||||
|
cfg.RuntimeConfigDir = filepath.ToSlash(cfg.RuntimeConfigDir)
|
||||||
|
}
|
||||||
if usesSlashPath(cfg.StatePath) {
|
if usesSlashPath(cfg.StatePath) {
|
||||||
cfg.StatePath = filepath.ToSlash(cfg.StatePath)
|
cfg.StatePath = filepath.ToSlash(cfg.StatePath)
|
||||||
}
|
}
|
||||||
@@ -230,6 +236,75 @@ func normalizeManagedPaths(cfg *Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasEnvConfig() bool {
|
||||||
|
for _, key := range []string{
|
||||||
|
"OPENFLARE_SERVER_URL",
|
||||||
|
"OPENFLARE_AGENT_TOKEN",
|
||||||
|
"OPENFLARE_DISCOVERY_TOKEN",
|
||||||
|
"OPENFLARE_NODE_NAME",
|
||||||
|
"OPENFLARE_NODE_IP",
|
||||||
|
"OPENFLARE_DATA_DIR",
|
||||||
|
"OPENFLARE_OPENRESTY_PATH",
|
||||||
|
"OPENFLARE_HEARTBEAT_INTERVAL",
|
||||||
|
"OPENFLARE_REQUEST_TIMEOUT",
|
||||||
|
"OPENFLARE_OPENRESTY_OBSERVABILITY_PORT",
|
||||||
|
} {
|
||||||
|
if strings.TrimSpace(os.Getenv(key)) != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyEnvOverrides(cfg *Config) {
|
||||||
|
if cfg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
overrideString := func(key string, target *string) {
|
||||||
|
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
|
||||||
|
*target = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overrideString("OPENFLARE_SERVER_URL", &cfg.ServerURL)
|
||||||
|
overrideString("OPENFLARE_AGENT_TOKEN", &cfg.AgentToken)
|
||||||
|
overrideString("OPENFLARE_DISCOVERY_TOKEN", &cfg.DiscoveryToken)
|
||||||
|
overrideString("OPENFLARE_NODE_NAME", &cfg.NodeName)
|
||||||
|
overrideString("OPENFLARE_NODE_IP", &cfg.NodeIP)
|
||||||
|
overrideString("OPENFLARE_DATA_DIR", &cfg.DataDir)
|
||||||
|
overrideString("OPENFLARE_OPENRESTY_PATH", &cfg.OpenrestyPath)
|
||||||
|
if value := strings.TrimSpace(os.Getenv("OPENFLARE_HEARTBEAT_INTERVAL")); value != "" {
|
||||||
|
if duration, err := parseDurationValue(value); err == nil {
|
||||||
|
cfg.HeartbeatInterval = duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value := strings.TrimSpace(os.Getenv("OPENFLARE_REQUEST_TIMEOUT")); value != "" {
|
||||||
|
if duration, err := parseDurationValue(value); err == nil {
|
||||||
|
cfg.RequestTimeout = duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value := strings.TrimSpace(os.Getenv("OPENFLARE_OPENRESTY_OBSERVABILITY_PORT")); value != "" {
|
||||||
|
var port int
|
||||||
|
if _, err := fmt.Sscanf(value, "%d", &port); err == nil {
|
||||||
|
cfg.OpenrestyObservabilityPort = port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDurationValue(value string) (MillisecondDuration, error) {
|
||||||
|
trimmed := strings.TrimSpace(value)
|
||||||
|
if trimmed == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if parsed, err := time.ParseDuration(trimmed); err == nil {
|
||||||
|
return MillisecondDuration(parsed), nil
|
||||||
|
}
|
||||||
|
ms, err := strconv.ParseInt(trimmed, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return MillisecondDuration(time.Duration(ms) * time.Millisecond), nil
|
||||||
|
}
|
||||||
|
|
||||||
func usesSlashPath(path string) bool {
|
func usesSlashPath(path string) bool {
|
||||||
return strings.HasPrefix(path, "/")
|
return strings.HasPrefix(path, "/")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadDockerModeUsesManagedPaths(t *testing.T) {
|
func TestLoadDefaultsToManagedBinaryPaths(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
configPath := filepath.Join(dir, "agent.json")
|
configPath := filepath.Join(dir, "agent.json")
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
@@ -34,31 +34,34 @@ func TestLoadDockerModeUsesManagedPaths(t *testing.T) {
|
|||||||
if cfg.DataDir != filepath.Join(dir, "data") {
|
if cfg.DataDir != filepath.Join(dir, "data") {
|
||||||
t.Fatalf("unexpected data dir: %s", cfg.DataDir)
|
t.Fatalf("unexpected data dir: %s", cfg.DataDir)
|
||||||
}
|
}
|
||||||
if cfg.MainConfigPath != filepath.Join(dir, "data", defaultDockerMainConfigRelativePath) {
|
if cfg.OpenrestyPath != "openresty" {
|
||||||
|
t.Fatalf("unexpected openresty path: %s", cfg.OpenrestyPath)
|
||||||
|
}
|
||||||
|
if cfg.MainConfigPath != filepath.Join(dir, "data", defaultMainConfigRelativePath) {
|
||||||
t.Fatalf("unexpected main config path: %s", cfg.MainConfigPath)
|
t.Fatalf("unexpected main config path: %s", cfg.MainConfigPath)
|
||||||
}
|
}
|
||||||
if cfg.RouteConfigPath != filepath.Join(dir, "data", defaultDockerRouteConfigRelativePath) {
|
if cfg.RouteConfigPath != filepath.Join(dir, "data", defaultRouteConfigRelativePath) {
|
||||||
t.Fatalf("unexpected route config path: %s", cfg.RouteConfigPath)
|
t.Fatalf("unexpected route config path: %s", cfg.RouteConfigPath)
|
||||||
}
|
}
|
||||||
|
if cfg.AccessLogPath != filepath.Join(dir, "data", defaultAccessLogRelativePath) {
|
||||||
|
t.Fatalf("unexpected access log path: %s", cfg.AccessLogPath)
|
||||||
|
}
|
||||||
if cfg.CertDir != filepath.Join(dir, "data", defaultCertDirRelativePath) {
|
if cfg.CertDir != filepath.Join(dir, "data", defaultCertDirRelativePath) {
|
||||||
t.Fatalf("unexpected cert dir: %s", cfg.CertDir)
|
t.Fatalf("unexpected cert dir: %s", cfg.CertDir)
|
||||||
}
|
}
|
||||||
if cfg.LuaDir != filepath.Join(dir, "data", defaultLuaDirRelativePath) {
|
if cfg.LuaDir != filepath.Join(dir, "data", defaultLuaDirRelativePath) {
|
||||||
t.Fatalf("unexpected lua dir: %s", cfg.LuaDir)
|
t.Fatalf("unexpected lua dir: %s", cfg.LuaDir)
|
||||||
}
|
}
|
||||||
if cfg.OpenrestyContainerName != "openflare-openresty" {
|
if cfg.RuntimeConfigDir != filepath.Join(dir, "data", defaultRuntimeConfigDirRelativePath) {
|
||||||
t.Fatalf("unexpected openresty container name: %s", cfg.OpenrestyContainerName)
|
t.Fatalf("unexpected runtime config dir: %s", cfg.RuntimeConfigDir)
|
||||||
}
|
}
|
||||||
if cfg.OpenrestyDockerImage != "openresty/openresty:alpine" {
|
if cfg.OpenrestyCertDir != cfg.CertDir {
|
||||||
t.Fatalf("unexpected openresty image: %s", cfg.OpenrestyDockerImage)
|
|
||||||
}
|
|
||||||
if cfg.OpenrestyCertDir != defaultDockerOpenRestyCertDir {
|
|
||||||
t.Fatalf("unexpected openresty cert dir: %s", cfg.OpenrestyCertDir)
|
t.Fatalf("unexpected openresty cert dir: %s", cfg.OpenrestyCertDir)
|
||||||
}
|
}
|
||||||
if cfg.OpenrestyLuaDir != defaultDockerOpenRestyLuaDir {
|
if cfg.OpenrestyLuaDir != cfg.LuaDir {
|
||||||
t.Fatalf("unexpected openresty lua dir: %s", cfg.OpenrestyLuaDir)
|
t.Fatalf("unexpected openresty lua dir: %s", cfg.OpenrestyLuaDir)
|
||||||
}
|
}
|
||||||
if cfg.StatePath != filepath.Join(dir, "data", defaultDockerStateRelativePath) {
|
if cfg.StatePath != filepath.Join(dir, "data", defaultStateRelativePath) {
|
||||||
t.Fatalf("unexpected state path: %s", cfg.StatePath)
|
t.Fatalf("unexpected state path: %s", cfg.StatePath)
|
||||||
}
|
}
|
||||||
if cfg.ObservabilityBufferPath != filepath.Join(dir, "data", defaultObservabilityBufferRelativePath) {
|
if cfg.ObservabilityBufferPath != filepath.Join(dir, "data", defaultObservabilityBufferRelativePath) {
|
||||||
@@ -155,6 +158,41 @@ func TestLoadNormalizesExplicitResolvers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadKeepsDeprecatedDockerFieldsForCompatibility(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
configPath := filepath.Join(dir, "agent.json")
|
||||||
|
payload := map[string]any{
|
||||||
|
"server_url": "http://127.0.0.1:3000",
|
||||||
|
"agent_token": "token",
|
||||||
|
"node_name": "edge-01",
|
||||||
|
"node_ip": "10.0.0.8",
|
||||||
|
"openresty_container_name": "openflare-openresty",
|
||||||
|
"openresty_docker_image": "openresty/openresty:alpine",
|
||||||
|
"docker_binary": "docker",
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal config: %v", err)
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(configPath, data, 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := Load(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load failed: %v", err)
|
||||||
|
}
|
||||||
|
if cfg.OpenrestyContainerName != "openflare-openresty" {
|
||||||
|
t.Fatalf("unexpected container name: %s", cfg.OpenrestyContainerName)
|
||||||
|
}
|
||||||
|
if cfg.OpenrestyDockerImage != "openresty/openresty:alpine" {
|
||||||
|
t.Fatalf("unexpected image: %s", cfg.OpenrestyDockerImage)
|
||||||
|
}
|
||||||
|
if cfg.DockerBinary != "docker" {
|
||||||
|
t.Fatalf("unexpected docker binary: %s", cfg.DockerBinary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadUsesCustomDataDirForGeneratedFiles(t *testing.T) {
|
func TestLoadUsesCustomDataDirForGeneratedFiles(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
configPath := filepath.Join(dir, "agent.json")
|
configPath := filepath.Join(dir, "agent.json")
|
||||||
@@ -178,13 +216,16 @@ func TestLoadUsesCustomDataDirForGeneratedFiles(t *testing.T) {
|
|||||||
t.Fatalf("Load failed: %v", err)
|
t.Fatalf("Load failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.RouteConfigPath != "/srv/openflare/"+defaultDockerRouteConfigRelativePath {
|
if cfg.RouteConfigPath != "/srv/openflare/"+defaultRouteConfigRelativePath {
|
||||||
t.Fatalf("unexpected route config path: %s", cfg.RouteConfigPath)
|
t.Fatalf("unexpected route config path: %s", cfg.RouteConfigPath)
|
||||||
}
|
}
|
||||||
if cfg.MainConfigPath != "/srv/openflare/"+defaultDockerMainConfigRelativePath {
|
if cfg.MainConfigPath != "/srv/openflare/"+defaultMainConfigRelativePath {
|
||||||
t.Fatalf("unexpected main config path: %s", cfg.MainConfigPath)
|
t.Fatalf("unexpected main config path: %s", cfg.MainConfigPath)
|
||||||
}
|
}
|
||||||
if cfg.StatePath != "/srv/openflare/"+defaultDockerStateRelativePath {
|
if cfg.AccessLogPath != "/srv/openflare/"+defaultAccessLogRelativePath {
|
||||||
|
t.Fatalf("unexpected access log path: %s", cfg.AccessLogPath)
|
||||||
|
}
|
||||||
|
if cfg.StatePath != "/srv/openflare/"+defaultStateRelativePath {
|
||||||
t.Fatalf("unexpected state path: %s", cfg.StatePath)
|
t.Fatalf("unexpected state path: %s", cfg.StatePath)
|
||||||
}
|
}
|
||||||
if cfg.ObservabilityBufferPath != "/srv/openflare/"+defaultObservabilityBufferRelativePath {
|
if cfg.ObservabilityBufferPath != "/srv/openflare/"+defaultObservabilityBufferRelativePath {
|
||||||
@@ -196,6 +237,70 @@ func TestLoadUsesCustomDataDirForGeneratedFiles(t *testing.T) {
|
|||||||
if cfg.LuaDir != "/srv/openflare/"+defaultLuaDirRelativePath {
|
if cfg.LuaDir != "/srv/openflare/"+defaultLuaDirRelativePath {
|
||||||
t.Fatalf("unexpected lua dir: %s", cfg.LuaDir)
|
t.Fatalf("unexpected lua dir: %s", cfg.LuaDir)
|
||||||
}
|
}
|
||||||
|
if cfg.RuntimeConfigDir != "/srv/openflare/"+defaultRuntimeConfigDirRelativePath {
|
||||||
|
t.Fatalf("unexpected runtime config dir: %s", cfg.RuntimeConfigDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadUsesEnvConfigWhenFileIsMissing(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
t.Setenv("OPENFLARE_SERVER_URL", "http://127.0.0.1:3000")
|
||||||
|
t.Setenv("OPENFLARE_AGENT_TOKEN", "token")
|
||||||
|
t.Setenv("OPENFLARE_NODE_NAME", "edge-env")
|
||||||
|
t.Setenv("OPENFLARE_NODE_IP", "10.0.0.9")
|
||||||
|
t.Setenv("OPENFLARE_DATA_DIR", "/srv/openflare-env")
|
||||||
|
t.Setenv("OPENFLARE_OPENRESTY_PATH", "/usr/bin/openresty")
|
||||||
|
t.Setenv("OPENFLARE_HEARTBEAT_INTERVAL", "45s")
|
||||||
|
t.Setenv("OPENFLARE_REQUEST_TIMEOUT", "2500")
|
||||||
|
t.Setenv("OPENFLARE_OPENRESTY_OBSERVABILITY_PORT", "19091")
|
||||||
|
|
||||||
|
cfg, err := Load(filepath.Join(dir, "missing-agent.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load failed: %v", err)
|
||||||
|
}
|
||||||
|
if cfg.ServerURL != "http://127.0.0.1:3000" || cfg.AgentToken != "token" {
|
||||||
|
t.Fatalf("unexpected env auth config: %#v", cfg)
|
||||||
|
}
|
||||||
|
if cfg.OpenrestyPath != "/usr/bin/openresty" {
|
||||||
|
t.Fatalf("unexpected openresty path: %s", cfg.OpenrestyPath)
|
||||||
|
}
|
||||||
|
if cfg.DataDir != "/srv/openflare-env" {
|
||||||
|
t.Fatalf("unexpected data dir: %s", cfg.DataDir)
|
||||||
|
}
|
||||||
|
if cfg.HeartbeatInterval.Duration() != 45*time.Second {
|
||||||
|
t.Fatalf("unexpected heartbeat interval: %s", cfg.HeartbeatInterval)
|
||||||
|
}
|
||||||
|
if cfg.RequestTimeout.Duration() != 2500*time.Millisecond {
|
||||||
|
t.Fatalf("unexpected request timeout: %s", cfg.RequestTimeout)
|
||||||
|
}
|
||||||
|
if cfg.OpenrestyObservabilityPort != 19091 {
|
||||||
|
t.Fatalf("unexpected observability port: %d", cfg.OpenrestyObservabilityPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadEnvOverridesConfigFile(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
configPath := filepath.Join(dir, "agent.json")
|
||||||
|
if err := os.WriteFile(configPath, []byte(`{"server_url":"http://old:3000","agent_token":"old","node_name":"edge-01","node_ip":"10.0.0.8","openresty_path":"/old/openresty"}`), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write config: %v", err)
|
||||||
|
}
|
||||||
|
t.Setenv("OPENFLARE_SERVER_URL", "http://new:3000")
|
||||||
|
t.Setenv("OPENFLARE_AGENT_TOKEN", "new-token")
|
||||||
|
t.Setenv("OPENFLARE_OPENRESTY_PATH", "/new/openresty")
|
||||||
|
|
||||||
|
cfg, err := Load(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load failed: %v", err)
|
||||||
|
}
|
||||||
|
if cfg.ServerURL != "http://new:3000" {
|
||||||
|
t.Fatalf("expected server url from env, got %s", cfg.ServerURL)
|
||||||
|
}
|
||||||
|
if cfg.AgentToken != "new-token" {
|
||||||
|
t.Fatalf("expected token from env, got %s", cfg.AgentToken)
|
||||||
|
}
|
||||||
|
if cfg.OpenrestyPath != "/new/openresty" {
|
||||||
|
t.Fatalf("expected openresty path from env, got %s", cfg.OpenrestyPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadUsesMillisecondsForIntervals(t *testing.T) {
|
func TestLoadUsesMillisecondsForIntervals(t *testing.T) {
|
||||||
@@ -283,6 +388,15 @@ func TestSavePersistsMillisecondsAndOmitsRuntimeVersions(t *testing.T) {
|
|||||||
if _, ok := decoded["nginx_path"]; ok {
|
if _, ok := decoded["nginx_path"]; ok {
|
||||||
t.Fatal("legacy nginx_path should not be persisted")
|
t.Fatal("legacy nginx_path should not be persisted")
|
||||||
}
|
}
|
||||||
|
if _, ok := decoded["openresty_container_name"]; ok {
|
||||||
|
t.Fatal("deprecated openresty_container_name should not be persisted by default")
|
||||||
|
}
|
||||||
|
if _, ok := decoded["openresty_docker_image"]; ok {
|
||||||
|
t.Fatal("deprecated openresty_docker_image should not be persisted by default")
|
||||||
|
}
|
||||||
|
if _, ok := decoded["docker_binary"]; ok {
|
||||||
|
t.Fatal("deprecated docker_binary should not be persisted by default")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInitialAuthToken(t *testing.T) {
|
func TestInitialAuthToken(t *testing.T) {
|
||||||
|
|||||||
@@ -24,15 +24,11 @@ const CertDirPlaceholder = "__OPENFLARE_CERT_DIR__"
|
|||||||
const RouteConfigPlaceholder = "__OPENFLARE_ROUTE_CONFIG__"
|
const RouteConfigPlaceholder = "__OPENFLARE_ROUTE_CONFIG__"
|
||||||
const AccessLogPlaceholder = "__OPENFLARE_ACCESS_LOG__"
|
const AccessLogPlaceholder = "__OPENFLARE_ACCESS_LOG__"
|
||||||
const LuaDirPlaceholder = "__OPENFLARE_LUA_DIR__"
|
const LuaDirPlaceholder = "__OPENFLARE_LUA_DIR__"
|
||||||
|
const RuntimeConfigDirPlaceholder = "__OPENFLARE_RUNTIME_CONFIG_DIR__"
|
||||||
const ObservabilityListenPlaceholder = "__OPENFLARE_OBSERVABILITY_LISTEN__"
|
const ObservabilityListenPlaceholder = "__OPENFLARE_OBSERVABILITY_LISTEN__"
|
||||||
const ObservabilityPortPlaceholder = "__OPENFLARE_OBSERVABILITY_PORT__"
|
const ObservabilityPortPlaceholder = "__OPENFLARE_OBSERVABILITY_PORT__"
|
||||||
const ResolverDirectivePlaceholder = "__OPENFLARE_RESOLVER_DIRECTIVE__"
|
const ResolverDirectivePlaceholder = "__OPENFLARE_RESOLVER_DIRECTIVE__"
|
||||||
const PowStaticDirPlaceholder = "__OPENFLARE_POW_STATIC_DIR__"
|
const PowStaticDirPlaceholder = "__OPENFLARE_POW_STATIC_DIR__"
|
||||||
const DockerMainConfigPath = "/usr/local/openresty/nginx/conf/nginx.conf"
|
|
||||||
const DockerRouteConfigPath = "/etc/nginx/conf.d/openflare_routes.conf"
|
|
||||||
const DockerAccessLogPath = "/etc/nginx/conf.d/openflare_access.log"
|
|
||||||
|
|
||||||
const dockerRuntimeCommand = "openresty"
|
|
||||||
|
|
||||||
type Executor interface {
|
type Executor interface {
|
||||||
Test(ctx context.Context) error
|
Test(ctx context.Context) error
|
||||||
@@ -55,13 +51,14 @@ func (r *OSCommandRunner) Run(ctx context.Context, name string, args ...string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PathExecutor struct {
|
type PathExecutor struct {
|
||||||
Path string
|
Path string
|
||||||
Runner CommandRunner
|
ConfigPath string
|
||||||
|
Runner CommandRunner
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *PathExecutor) Test(ctx context.Context) error {
|
func (e *PathExecutor) Test(ctx context.Context) error {
|
||||||
slog.Debug("running openresty test with binary", "path", e.Path)
|
slog.Debug("running openresty test with binary", "path", e.Path, "config", e.ConfigPath)
|
||||||
output, err := e.Runner.Run(ctx, e.Path, "-t")
|
output, err := e.Runner.Run(ctx, e.Path, "-t", "-c", e.ConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("openresty -t failed: %w: %s", err, string(output))
|
return fmt.Errorf("openresty -t failed: %w: %s", err, string(output))
|
||||||
}
|
}
|
||||||
@@ -70,9 +67,17 @@ func (e *PathExecutor) Test(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *PathExecutor) Reload(ctx context.Context) error {
|
func (e *PathExecutor) Reload(ctx context.Context) error {
|
||||||
slog.Debug("running openresty reload with binary", "path", e.Path)
|
slog.Debug("running openresty reload with binary", "path", e.Path, "config", e.ConfigPath)
|
||||||
output, err := e.Runner.Run(ctx, e.Path, "-s", "reload")
|
output, err := e.Runner.Run(ctx, e.Path, "-s", "reload", "-c", e.ConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isOpenrestyNotRunningError(string(output)) {
|
||||||
|
slog.Warn("openresty reload reported runtime is not running, starting binary", "path", e.Path)
|
||||||
|
startOutput, startErr := e.Runner.Run(ctx, e.Path, "-c", e.ConfigPath)
|
||||||
|
if startErr != nil {
|
||||||
|
return fmt.Errorf("openresty reload failed: %w: %s; start failed: %v: %s", err, string(output), startErr, string(startOutput))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return fmt.Errorf("openresty reload failed: %w: %s", err, string(output))
|
return fmt.Errorf("openresty reload failed: %w: %s", err, string(output))
|
||||||
}
|
}
|
||||||
slog.Debug("openresty reload succeeded with binary", "path", e.Path)
|
slog.Debug("openresty reload succeeded with binary", "path", e.Path)
|
||||||
@@ -80,7 +85,10 @@ func (e *PathExecutor) Reload(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *PathExecutor) EnsureRuntime(ctx context.Context, recreate bool) error {
|
func (e *PathExecutor) EnsureRuntime(ctx context.Context, recreate bool) error {
|
||||||
return nil
|
if err := e.Test(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.Reload(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *PathExecutor) CheckHealth(ctx context.Context) error {
|
func (e *PathExecutor) CheckHealth(ctx context.Context) error {
|
||||||
@@ -88,15 +96,15 @@ func (e *PathExecutor) CheckHealth(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *PathExecutor) Restart(ctx context.Context) error {
|
func (e *PathExecutor) Restart(ctx context.Context) error {
|
||||||
slog.Info("restarting openresty with binary", "path", e.Path)
|
slog.Info("restarting openresty with binary", "path", e.Path, "config", e.ConfigPath)
|
||||||
output, err := e.Runner.Run(ctx, e.Path, "-s", "quit")
|
output, err := e.Runner.Run(ctx, e.Path, "-s", "quit", "-c", e.ConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
text := string(output)
|
text := string(output)
|
||||||
if !isIgnorableOpenrestyStopError(text) {
|
if !isIgnorableOpenrestyStopError(text) {
|
||||||
return fmt.Errorf("openresty stop failed: %w: %s", err, text)
|
return fmt.Errorf("openresty stop failed: %w: %s", err, text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output, err = e.Runner.Run(ctx, e.Path)
|
output, err = e.Runner.Run(ctx, e.Path, "-c", e.ConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("openresty start failed: %w: %s", err, string(output))
|
return fmt.Errorf("openresty start failed: %w: %s", err, string(output))
|
||||||
}
|
}
|
||||||
@@ -104,258 +112,15 @@ func (e *PathExecutor) Restart(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type DockerExecutor struct {
|
|
||||||
DockerBinary string
|
|
||||||
ContainerName string
|
|
||||||
Image string
|
|
||||||
MainConfigPath string
|
|
||||||
RouteConfigDir string
|
|
||||||
CertDir string
|
|
||||||
NginxCertDir string
|
|
||||||
LuaDir string
|
|
||||||
NginxLuaDir string
|
|
||||||
OpenrestyObservabilityPort int
|
|
||||||
Runner CommandRunner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *DockerExecutor) Test(ctx context.Context) error {
|
|
||||||
slog.Debug("running docker openresty test", "container", e.ContainerName, "image", e.Image)
|
|
||||||
if err := e.validateMountSources(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
output, err := e.runEphemeralRuntimeCommand(ctx, "-t")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("docker %s -t failed: %w: %s", dockerRuntimeCommand, err, string(output))
|
|
||||||
}
|
|
||||||
slog.Debug("docker openresty test succeeded", "container", e.ContainerName, "runtime", dockerRuntimeCommand)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *DockerExecutor) Reload(ctx context.Context) error {
|
|
||||||
if err := e.validateMountSources(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
output, err := e.Runner.Run(ctx, e.DockerBinary, "inspect", "-f", "{{.State.Running}}", e.ContainerName)
|
|
||||||
if err != nil || strings.TrimSpace(string(output)) != "true" {
|
|
||||||
return e.EnsureRuntime(ctx, false)
|
|
||||||
}
|
|
||||||
output, err = e.Runner.Run(ctx, e.DockerBinary, "exec", e.ContainerName, dockerRuntimeCommand, "-s", "reload")
|
|
||||||
if err != nil {
|
|
||||||
if e.shouldRecreateAfterReloadFailure(string(output)) {
|
|
||||||
slog.Warn("docker openresty reload failed due to missing mounted files, recreating container", "container", e.ContainerName)
|
|
||||||
if recreateErr := e.EnsureRuntime(ctx, true); recreateErr != nil {
|
|
||||||
return fmt.Errorf("docker exec %s reload failed: %w: %s; recreate failed: %v", dockerRuntimeCommand, err, string(output), recreateErr)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("docker exec %s reload failed: %w: %s", dockerRuntimeCommand, err, string(output))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *DockerExecutor) EnsureRuntime(ctx context.Context, recreate bool) error {
|
|
||||||
slog.Info("ensuring docker openresty runtime", "container", e.ContainerName, "recreate", recreate)
|
|
||||||
output, err := e.Runner.Run(ctx, e.DockerBinary, "inspect", "-f", "{{.State.Running}}", e.ContainerName)
|
|
||||||
if err == nil {
|
|
||||||
if recreate {
|
|
||||||
if err := e.removeContainer(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return e.runContainer(ctx)
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(string(output)) == "true" {
|
|
||||||
slog.Debug("docker openresty runtime already healthy", "container", e.ContainerName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := e.removeContainer(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return e.runContainer(ctx)
|
|
||||||
}
|
|
||||||
return e.runContainer(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *DockerExecutor) CheckHealth(ctx context.Context) error {
|
|
||||||
slog.Debug("checking docker openresty runtime health", "container", e.ContainerName)
|
|
||||||
output, err := e.Runner.Run(ctx, e.DockerBinary, "inspect", "-f", "{{.State.Running}}", e.ContainerName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("docker inspect openresty failed: %w: %s", err, string(output))
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(string(output)) != "true" {
|
|
||||||
return e.containerNotRunningError(ctx)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *DockerExecutor) Restart(ctx context.Context) error {
|
|
||||||
return e.EnsureRuntime(ctx, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *DockerExecutor) removeContainer(ctx context.Context) error {
|
|
||||||
slog.Info("removing docker openresty container", "container", e.ContainerName)
|
|
||||||
output, err := e.Runner.Run(ctx, e.DockerBinary, "rm", "-f", e.ContainerName)
|
|
||||||
if err != nil {
|
|
||||||
text := string(output)
|
|
||||||
if strings.Contains(text, "No such container") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("docker rm openresty failed: %w: %s", err, text)
|
|
||||||
}
|
|
||||||
slog.Info("docker openresty container removed", "container", e.ContainerName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *DockerExecutor) runContainer(ctx context.Context) error {
|
|
||||||
slog.Info("starting docker openresty container", "container", e.ContainerName, "image", e.Image)
|
|
||||||
if err := e.validateMountSources(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
runArgs := []string{
|
|
||||||
"run", "-d",
|
|
||||||
"--name", e.ContainerName,
|
|
||||||
"--restart", "always",
|
|
||||||
"-p", "80:80",
|
|
||||||
"-p", "443:443",
|
|
||||||
"-p", fmt.Sprintf("127.0.0.1:%d:%d", e.OpenrestyObservabilityPort, e.OpenrestyObservabilityPort),
|
|
||||||
"-v", fmt.Sprintf("%s:%s", e.MainConfigPath, DockerMainConfigPath),
|
|
||||||
"-v", fmt.Sprintf("%s:/etc/nginx/conf.d", e.RouteConfigDir),
|
|
||||||
"-v", fmt.Sprintf("%s:%s", e.CertDir, e.NginxCertDir),
|
|
||||||
"-v", fmt.Sprintf("%s:%s", e.LuaDir, e.NginxLuaDir),
|
|
||||||
e.Image,
|
|
||||||
}
|
|
||||||
runOutput, runErr := e.Runner.Run(ctx, e.DockerBinary, runArgs...)
|
|
||||||
if runErr != nil {
|
|
||||||
return fmt.Errorf("docker run openresty failed: %w: %s", runErr, string(runOutput))
|
|
||||||
}
|
|
||||||
if err := e.CheckHealth(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
slog.Info("docker openresty container started", "container", e.ContainerName)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *DockerExecutor) validateMountSources() error {
|
|
||||||
if err := ensureRegularFile(e.MainConfigPath, "openresty main config"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ensureDirectory(e.RouteConfigDir, "openresty route config dir"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ensureDirectory(e.CertDir, "openresty cert dir"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ensureDirectory(e.LuaDir, "openresty lua dir"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *DockerExecutor) shouldRecreateAfterReloadFailure(output string) bool {
|
|
||||||
text := strings.ToLower(strings.TrimSpace(output))
|
|
||||||
if text == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !strings.Contains(text, "no such file") && !strings.Contains(text, "cannot load certificate") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
paths := []string{
|
|
||||||
strings.ToLower(e.NginxCertDir),
|
|
||||||
strings.ToLower(e.NginxLuaDir),
|
|
||||||
strings.ToLower(DockerMainConfigPath),
|
|
||||||
strings.ToLower("/etc/nginx/conf.d"),
|
|
||||||
}
|
|
||||||
for _, path := range paths {
|
|
||||||
if strings.TrimSpace(path) != "" && strings.Contains(text, path) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *DockerExecutor) containerNotRunningError(ctx context.Context) error {
|
|
||||||
inspectSummary := ""
|
|
||||||
inspectOutput, inspectErr := e.Runner.Run(ctx, e.DockerBinary, "inspect", "-f", "status={{.State.Status}} exit_code={{.State.ExitCode}} error={{printf \"%q\" .State.Error}} oom_killed={{.State.OOMKilled}} finished_at={{.State.FinishedAt}}", e.ContainerName)
|
|
||||||
if inspectErr == nil {
|
|
||||||
inspectSummary = strings.TrimSpace(string(inspectOutput))
|
|
||||||
}
|
|
||||||
|
|
||||||
logTail := ""
|
|
||||||
logOutput, logErr := e.Runner.Run(ctx, e.DockerBinary, "logs", "--tail", "50", e.ContainerName)
|
|
||||||
if logErr == nil {
|
|
||||||
logTail = strings.TrimSpace(string(logOutput))
|
|
||||||
}
|
|
||||||
|
|
||||||
message := "docker openresty container is not running"
|
|
||||||
if inspectSummary != "" {
|
|
||||||
message += ": " + inspectSummary
|
|
||||||
}
|
|
||||||
if logTail != "" {
|
|
||||||
message += "; recent logs: " + compactDiagnosticText(logTail)
|
|
||||||
}
|
|
||||||
return errors.New(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func compactDiagnosticText(text string) string {
|
|
||||||
trimmed := strings.TrimSpace(text)
|
|
||||||
if trimmed == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
lines := strings.Split(trimmed, "\n")
|
|
||||||
if len(lines) > 8 {
|
|
||||||
lines = lines[len(lines)-8:]
|
|
||||||
}
|
|
||||||
joined := strings.Join(lines, " | ")
|
|
||||||
joined = strings.Join(strings.Fields(joined), " ")
|
|
||||||
if len(joined) > 800 {
|
|
||||||
return joined[len(joined)-800:]
|
|
||||||
}
|
|
||||||
return joined
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureRegularFile(path string, label string) error {
|
|
||||||
cleanPath := strings.TrimSpace(path)
|
|
||||||
if cleanPath == "" {
|
|
||||||
return fmt.Errorf("%s path is empty", label)
|
|
||||||
}
|
|
||||||
info, err := os.Stat(cleanPath)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("%s %q does not exist; run a config apply first so Docker does not create a directory mount source", label, cleanPath)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("stat %s %q failed: %w", label, cleanPath, err)
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return fmt.Errorf("%s %q is a directory; expected a file for Docker bind mount", label, cleanPath)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureDirectory(path string, label string) error {
|
|
||||||
cleanPath := strings.TrimSpace(path)
|
|
||||||
if cleanPath == "" {
|
|
||||||
return fmt.Errorf("%s path is empty", label)
|
|
||||||
}
|
|
||||||
info, err := os.Stat(cleanPath)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("%s %q does not exist; expected a directory for Docker bind mount", label, cleanPath)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("stat %s %q failed: %w", label, cleanPath, err)
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
return fmt.Errorf("%s %q is not a directory; expected a directory for Docker bind mount", label, cleanPath)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
MainConfigPath string
|
MainConfigPath string
|
||||||
RouteConfigPath string
|
RouteConfigPath string
|
||||||
RuntimeRouteConfigPath string
|
AccessLogPath string
|
||||||
CertDir string
|
CertDir string
|
||||||
NginxCertDir string
|
NginxCertDir string
|
||||||
LuaDir string
|
LuaDir string
|
||||||
NginxLuaDir string
|
NginxLuaDir string
|
||||||
|
RuntimeConfigDir string
|
||||||
OpenrestyObservabilityListen string
|
OpenrestyObservabilityListen string
|
||||||
OpenrestyObservabilityPort int
|
OpenrestyObservabilityPort int
|
||||||
OpenrestyResolverDirective string
|
OpenrestyResolverDirective string
|
||||||
@@ -419,8 +184,8 @@ func (m *Manager) activateConfig(ctx context.Context) error {
|
|||||||
if m.Executor == nil {
|
if m.Executor == nil {
|
||||||
return errors.New("executor 未配置")
|
return errors.New("executor 未配置")
|
||||||
}
|
}
|
||||||
if _, ok := m.Executor.(*DockerExecutor); ok {
|
if err := m.Executor.Test(ctx); err != nil {
|
||||||
return m.Executor.EnsureRuntime(ctx, true)
|
return err
|
||||||
}
|
}
|
||||||
return m.Executor.Reload(ctx)
|
return m.Executor.Reload(ctx)
|
||||||
}
|
}
|
||||||
@@ -455,14 +220,7 @@ func (m *Manager) EnsureLuaAssets() error {
|
|||||||
if strings.TrimSpace(m.LuaDir) == "" {
|
if strings.TrimSpace(m.LuaDir) == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
allSupportFiles := append(ManagedObservabilityLuaFiles(), ManagedPowLuaFiles()...)
|
allSupportFiles := append(ManagedObservabilityLuaFiles(), m.managedPowLuaFiles()...)
|
||||||
existingPowConfig, err := m.readPowConfigFile()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if existingPowConfig != nil {
|
|
||||||
allSupportFiles = append(allSupportFiles, *existingPowConfig)
|
|
||||||
}
|
|
||||||
powStaticFiles, err := ManagedPowStaticFiles()
|
powStaticFiles, err := ManagedPowStaticFiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("load pow static files: %w", err)
|
return fmt.Errorf("load pow static files: %w", err)
|
||||||
@@ -569,9 +327,6 @@ func (m *Manager) CurrentChecksum() (string, error) {
|
|||||||
|
|
||||||
type ExecutorOptions struct {
|
type ExecutorOptions struct {
|
||||||
NginxPath string
|
NginxPath string
|
||||||
DockerBinary string
|
|
||||||
ContainerName string
|
|
||||||
Image string
|
|
||||||
MainConfigPath string
|
MainConfigPath string
|
||||||
RouteConfigPath string
|
RouteConfigPath string
|
||||||
CertDir string
|
CertDir string
|
||||||
@@ -583,48 +338,10 @@ type ExecutorOptions struct {
|
|||||||
|
|
||||||
func NewExecutor(options ExecutorOptions) Executor {
|
func NewExecutor(options ExecutorOptions) Executor {
|
||||||
runner := &OSCommandRunner{}
|
runner := &OSCommandRunner{}
|
||||||
if options.NginxPath != "" {
|
return &PathExecutor{
|
||||||
return &PathExecutor{
|
Path: strings.TrimSpace(options.NginxPath),
|
||||||
Path: options.NginxPath,
|
ConfigPath: strings.TrimSpace(options.MainConfigPath),
|
||||||
Runner: runner,
|
Runner: runner,
|
||||||
}
|
|
||||||
}
|
|
||||||
mainConfigPath := options.MainConfigPath
|
|
||||||
if mainConfigPath != "" {
|
|
||||||
if absPath, err := filepath.Abs(mainConfigPath); err == nil {
|
|
||||||
mainConfigPath = absPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
routeConfigDir := filepath.Dir(options.RouteConfigPath)
|
|
||||||
if options.RouteConfigPath != "" {
|
|
||||||
if absDir, err := filepath.Abs(routeConfigDir); err == nil {
|
|
||||||
routeConfigDir = absDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
certDir := options.CertDir
|
|
||||||
if certDir != "" {
|
|
||||||
if absDir, err := filepath.Abs(certDir); err == nil {
|
|
||||||
certDir = absDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
luaDir := options.LuaDir
|
|
||||||
if luaDir != "" {
|
|
||||||
if absDir, err := filepath.Abs(luaDir); err == nil {
|
|
||||||
luaDir = absDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &DockerExecutor{
|
|
||||||
DockerBinary: options.DockerBinary,
|
|
||||||
ContainerName: options.ContainerName,
|
|
||||||
Image: options.Image,
|
|
||||||
MainConfigPath: mainConfigPath,
|
|
||||||
RouteConfigDir: routeConfigDir,
|
|
||||||
CertDir: certDir,
|
|
||||||
NginxCertDir: options.NginxCertDir,
|
|
||||||
LuaDir: luaDir,
|
|
||||||
NginxLuaDir: options.NginxLuaDir,
|
|
||||||
OpenrestyObservabilityPort: options.OpenrestyObservabilityPort,
|
|
||||||
Runner: runner,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,15 +370,7 @@ func detectVersion(ctx context.Context, options ExecutorOptions, runner CommandR
|
|||||||
}
|
}
|
||||||
return version, nil
|
return version, nil
|
||||||
}
|
}
|
||||||
output, err := runDockerVersionProbe(ctx, runner, options.DockerBinary, options.Image)
|
return "", errors.New("openresty path is empty")
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("run docker %s -v failed: %w: %s", dockerRuntimeCommand, err, string(output))
|
|
||||||
}
|
|
||||||
version := parseNginxVersion(string(output))
|
|
||||||
if version == "" {
|
|
||||||
return "", errors.New("cannot parse runtime version from docker output")
|
|
||||||
}
|
|
||||||
return version, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseNginxVersion(output string) string {
|
func parseNginxVersion(output string) string {
|
||||||
@@ -682,31 +391,14 @@ func isIgnorableOpenrestyStopError(output string) bool {
|
|||||||
return strings.Contains(text, "invalid pid") || strings.Contains(text, "no such process")
|
return strings.Contains(text, "invalid pid") || strings.Contains(text, "no such process")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *DockerExecutor) runEphemeralRuntimeCommand(ctx context.Context, args ...string) ([]byte, error) {
|
func isOpenrestyNotRunningError(output string) bool {
|
||||||
return e.runEphemeralRuntimeCommandWithBinary(ctx, dockerRuntimeCommand, args...)
|
text := strings.ToLower(strings.TrimSpace(output))
|
||||||
}
|
if text == "" {
|
||||||
|
return false
|
||||||
func (e *DockerExecutor) runEphemeralRuntimeCommandWithBinary(ctx context.Context, runtimeBinary string, args ...string) ([]byte, error) {
|
|
||||||
runtimeArgs := []string{
|
|
||||||
"run",
|
|
||||||
"--rm",
|
|
||||||
"-v",
|
|
||||||
fmt.Sprintf("%s:%s", e.MainConfigPath, DockerMainConfigPath),
|
|
||||||
"-v",
|
|
||||||
fmt.Sprintf("%s:/etc/nginx/conf.d", e.RouteConfigDir),
|
|
||||||
"-v",
|
|
||||||
fmt.Sprintf("%s:%s", e.CertDir, e.NginxCertDir),
|
|
||||||
"-v",
|
|
||||||
fmt.Sprintf("%s:%s", e.LuaDir, e.NginxLuaDir),
|
|
||||||
e.Image,
|
|
||||||
runtimeBinary,
|
|
||||||
}
|
}
|
||||||
runtimeArgs = append(runtimeArgs, args...)
|
return strings.Contains(text, "invalid pid") ||
|
||||||
return e.Runner.Run(ctx, e.DockerBinary, runtimeArgs...)
|
strings.Contains(text, "no such process") ||
|
||||||
}
|
strings.Contains(text, "open()") && strings.Contains(text, "nginx.pid") && strings.Contains(text, "failed")
|
||||||
|
|
||||||
func runDockerVersionProbe(ctx context.Context, runner CommandRunner, dockerBinary string, image string) ([]byte, error) {
|
|
||||||
return runner.Run(ctx, dockerBinary, "run", "--rm", image, dockerRuntimeCommand, "-v")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type backupState struct {
|
type backupState struct {
|
||||||
@@ -737,11 +429,21 @@ func (m *Manager) backup() (*backupState, error) {
|
|||||||
if err := os.MkdirAll(filepath.Dir(m.RouteConfigPath), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(m.RouteConfigPath), 0o755); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if m.AccessLogPath != "" {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(m.AccessLogPath), 0o755); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if m.CertDir != "" {
|
if m.CertDir != "" {
|
||||||
if err := os.MkdirAll(m.CertDir, 0o755); err != nil {
|
if err := os.MkdirAll(m.CertDir, 0o755); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if m.RuntimeConfigDir != "" {
|
||||||
|
if err := os.MkdirAll(m.RuntimeConfigDir, 0o755); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
state := &backupState{}
|
state := &backupState{}
|
||||||
mainData, err := os.ReadFile(m.MainConfigPath)
|
mainData, err := os.ReadFile(m.MainConfigPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -806,10 +508,10 @@ func (m *Manager) writeCertFiles(certFiles []protocol.SupportFile) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) writePowConfig(supportFiles []protocol.SupportFile) error {
|
func (m *Manager) writePowConfig(supportFiles []protocol.SupportFile) error {
|
||||||
if m.LuaDir == "" {
|
if m.RuntimeConfigDir == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
configPath := filepath.Join(m.LuaDir, "pow_config.json")
|
configPath := filepath.Join(m.RuntimeConfigDir, "pow_config.json")
|
||||||
for _, file := range supportFiles {
|
for _, file := range supportFiles {
|
||||||
if file.Path == "pow_config.json" {
|
if file.Path == "pow_config.json" {
|
||||||
if err := os.WriteFile(configPath, []byte(file.Content), 0o644); err != nil {
|
if err := os.WriteFile(configPath, []byte(file.Content), 0o644); err != nil {
|
||||||
@@ -822,12 +524,21 @@ func (m *Manager) writePowConfig(supportFiles []protocol.SupportFile) error {
|
|||||||
if err := os.Remove(configPath); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(configPath); err != nil && !os.IsNotExist(err) {
|
||||||
return fmt.Errorf("remove pow_config.json: %w", err)
|
return fmt.Errorf("remove pow_config.json: %w", err)
|
||||||
}
|
}
|
||||||
|
if err := removeLegacyPowConfig(filepath.Join(m.LuaDir, "pow_config.json")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := removeLegacyPowConfig(filepath.Join(m.CertDir, "pow_config.json")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) writeManagedCertFiles(certFiles []protocol.SupportFile) error {
|
func (m *Manager) writeManagedCertFiles(certFiles []protocol.SupportFile) error {
|
||||||
files := make([]managedFile, 0, len(certFiles))
|
files := make([]managedFile, 0, len(certFiles))
|
||||||
for _, file := range certFiles {
|
for _, file := range certFiles {
|
||||||
|
if file.Path == "pow_config.json" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
targetPath, err := m.certFileTargetPath(file.Path)
|
targetPath, err := m.certFileTargetPath(file.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -863,11 +574,14 @@ func (m *Manager) readCertFiles() ([]protocol.SupportFile, error) {
|
|||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
data, err := os.ReadFile(path)
|
relativePath, err := filepath.Rel(m.CertDir, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
relativePath, err := filepath.Rel(m.CertDir, path)
|
if filepath.ToSlash(relativePath) == "pow_config.json" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -887,10 +601,10 @@ func (m *Manager) readCertFiles() ([]protocol.SupportFile, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) readPowConfigFile() (*protocol.SupportFile, error) {
|
func (m *Manager) readPowConfigFile() (*protocol.SupportFile, error) {
|
||||||
if m.LuaDir == "" {
|
if m.RuntimeConfigDir == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
configPath := filepath.Join(m.LuaDir, "pow_config.json")
|
configPath := filepath.Join(m.RuntimeConfigDir, "pow_config.json")
|
||||||
data, err := os.ReadFile(configPath)
|
data, err := os.ReadFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -920,10 +634,10 @@ func (m *Manager) readManagedSupportFiles() ([]protocol.SupportFile, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) restorePowConfig(state *backupState) error {
|
func (m *Manager) restorePowConfig(state *backupState) error {
|
||||||
if state == nil || m.LuaDir == "" {
|
if state == nil || m.RuntimeConfigDir == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
configPath := filepath.Join(m.LuaDir, "pow_config.json")
|
configPath := filepath.Join(m.RuntimeConfigDir, "pow_config.json")
|
||||||
if state.PowConfig == nil {
|
if state.PowConfig == nil {
|
||||||
if err := os.Remove(configPath); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(configPath); err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
@@ -933,6 +647,16 @@ func (m *Manager) restorePowConfig(state *backupState) error {
|
|||||||
return os.WriteFile(configPath, []byte(state.PowConfig.Content), 0o644)
|
return os.WriteFile(configPath, []byte(state.PowConfig.Content), 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeLegacyPowConfig(path string) error {
|
||||||
|
if strings.TrimSpace(path) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("remove legacy pow_config.json %q: %w", path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) certFileTargetPath(relativePath string) (string, error) {
|
func (m *Manager) certFileTargetPath(relativePath string) (string, error) {
|
||||||
if strings.TrimSpace(m.CertDir) == "" {
|
if strings.TrimSpace(m.CertDir) == "" {
|
||||||
return "", errors.New("cert dir 不能为空")
|
return "", errors.New("cert dir 不能为空")
|
||||||
@@ -1119,14 +843,20 @@ func (m *Manager) renderMainConfig(content string) string {
|
|||||||
return rendered
|
return rendered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) managedPowLuaFiles() []protocol.SupportFile {
|
||||||
|
files := ManagedPowLuaFiles()
|
||||||
|
runtimeConfigDir := filepath.ToSlash(strings.TrimSpace(m.RuntimeConfigDir))
|
||||||
|
for index := range files {
|
||||||
|
files[index].Content = strings.ReplaceAll(files[index].Content, RuntimeConfigDirPlaceholder, runtimeConfigDir)
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
func ObservabilityListenAddress(openrestyPath string, port int) string {
|
func ObservabilityListenAddress(openrestyPath string, port int) string {
|
||||||
if port <= 0 {
|
if port <= 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(openrestyPath) != "" {
|
return fmt.Sprintf("127.0.0.1:%d", port)
|
||||||
return fmt.Sprintf("127.0.0.1:%d", port)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d", port)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResolverDirective(openrestyPath string, explicitResolvers []string) string {
|
func ResolverDirective(openrestyPath string, explicitResolvers []string) string {
|
||||||
@@ -1145,7 +875,7 @@ func resolverAddresses(openrestyPath string, explicitResolvers []string) []strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return parseResolverAddresses(string(data), strings.TrimSpace(openrestyPath) == "")
|
return parseResolverAddresses(string(data), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseResolverAddresses(content string, dockerMode bool) []string {
|
func parseResolverAddresses(content string, dockerMode bool) []string {
|
||||||
@@ -1213,18 +943,11 @@ func RequiresRuntimeResolver(originURL string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) routeConfigIncludePath() string {
|
func (m *Manager) routeConfigIncludePath() string {
|
||||||
if strings.TrimSpace(m.RuntimeRouteConfigPath) != "" {
|
|
||||||
return strings.TrimSpace(m.RuntimeRouteConfigPath)
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(m.RouteConfigPath)
|
return strings.TrimSpace(m.RouteConfigPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) accessLogRuntimePath() string {
|
func (m *Manager) accessLogRuntimePath() string {
|
||||||
includePath := m.routeConfigIncludePath()
|
return filepath.ToSlash(strings.TrimSpace(m.AccessLogPath))
|
||||||
if strings.TrimSpace(includePath) == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return filepath.ToSlash(filepath.Join(filepath.Dir(includePath), "openflare_access.log"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) luaRuntimePath() string {
|
func (m *Manager) luaRuntimePath() string {
|
||||||
|
|||||||
@@ -89,8 +89,9 @@ func (e *scriptedExecutor) Restart(ctx context.Context) error {
|
|||||||
func TestPathExecutorCommands(t *testing.T) {
|
func TestPathExecutorCommands(t *testing.T) {
|
||||||
runner := &fakeRunner{}
|
runner := &fakeRunner{}
|
||||||
executor := &PathExecutor{
|
executor := &PathExecutor{
|
||||||
Path: "/usr/local/openresty/nginx/sbin/openresty",
|
Path: "/usr/local/openresty/nginx/sbin/openresty",
|
||||||
Runner: runner,
|
ConfigPath: "/data/etc/nginx/nginx.conf",
|
||||||
|
Runner: runner,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := executor.Test(context.Background()); err != nil {
|
if err := executor.Test(context.Background()); err != nil {
|
||||||
@@ -101,8 +102,8 @@ func TestPathExecutorCommands(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expected := []runCall{
|
expected := []runCall{
|
||||||
{name: "/usr/local/openresty/nginx/sbin/openresty", args: []string{"-t"}},
|
{name: "/usr/local/openresty/nginx/sbin/openresty", args: []string{"-t", "-c", "/data/etc/nginx/nginx.conf"}},
|
||||||
{name: "/usr/local/openresty/nginx/sbin/openresty", args: []string{"-s", "reload"}},
|
{name: "/usr/local/openresty/nginx/sbin/openresty", args: []string{"-s", "reload", "-c", "/data/etc/nginx/nginx.conf"}},
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(runner.calls, expected) {
|
if !reflect.DeepEqual(runner.calls, expected) {
|
||||||
t.Fatalf("unexpected calls: %#v", runner.calls)
|
t.Fatalf("unexpected calls: %#v", runner.calls)
|
||||||
@@ -110,13 +111,18 @@ func TestPathExecutorCommands(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPathExecutorEnsureRuntimeNoop(t *testing.T) {
|
func TestPathExecutorEnsureRuntimeNoop(t *testing.T) {
|
||||||
|
runner := &fakeRunner{}
|
||||||
executor := &PathExecutor{
|
executor := &PathExecutor{
|
||||||
Path: "/usr/local/openresty/nginx/sbin/openresty",
|
Path: "/usr/local/openresty/nginx/sbin/openresty",
|
||||||
Runner: &fakeRunner{},
|
ConfigPath: "/data/etc/nginx/nginx.conf",
|
||||||
|
Runner: runner,
|
||||||
}
|
}
|
||||||
if err := executor.EnsureRuntime(context.Background(), true); err != nil {
|
if err := executor.EnsureRuntime(context.Background(), true); err != nil {
|
||||||
t.Fatalf("EnsureRuntime failed: %v", err)
|
t.Fatalf("EnsureRuntime failed: %v", err)
|
||||||
}
|
}
|
||||||
|
if len(runner.calls) != 2 {
|
||||||
|
t.Fatalf("expected test and reload calls, got %d", len(runner.calls))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathExecutorRestartIgnoresMissingPID(t *testing.T) {
|
func TestPathExecutorRestartIgnoresMissingPID(t *testing.T) {
|
||||||
@@ -129,8 +135,9 @@ func TestPathExecutorRestartIgnoresMissingPID(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
executor := &PathExecutor{
|
executor := &PathExecutor{
|
||||||
Path: "/usr/local/openresty/nginx/sbin/openresty",
|
Path: "/usr/local/openresty/nginx/sbin/openresty",
|
||||||
Runner: runner,
|
ConfigPath: "/data/etc/nginx/nginx.conf",
|
||||||
|
Runner: runner,
|
||||||
}
|
}
|
||||||
if err := executor.Restart(context.Background()); err != nil {
|
if err := executor.Restart(context.Background()); err != nil {
|
||||||
t.Fatalf("Restart failed: %v", err)
|
t.Fatalf("Restart failed: %v", err)
|
||||||
@@ -140,423 +147,32 @@ func TestPathExecutorRestartIgnoresMissingPID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDockerExecutorCheckHealthFailsWhenContainerStopped(t *testing.T) {
|
func TestPathExecutorReloadStartsWhenRuntimeIsNotRunning(t *testing.T) {
|
||||||
runner := &fakeRunner{
|
runner := &fakeRunner{
|
||||||
runFn: func(name string, args ...string) ([]byte, error) {
|
runFn: func(name string, args ...string) ([]byte, error) {
|
||||||
if len(args) >= 4 && args[0] == "inspect" && args[2] == "{{.State.Running}}" {
|
if len(args) >= 2 && args[0] == "-s" && args[1] == "reload" {
|
||||||
return []byte("false"), nil
|
return []byte("openresty: [error] invalid PID number \"\" in \"/usr/local/openresty/nginx/logs/nginx.pid\""), errors.New("exit status 1")
|
||||||
}
|
}
|
||||||
if len(args) >= 4 && args[0] == "inspect" {
|
return []byte(""), nil
|
||||||
return []byte("status=exited exit_code=1 error=\"\" oom_killed=false finished_at=2026-03-18T10:08:30Z"), nil
|
|
||||||
}
|
|
||||||
if len(args) >= 1 && args[0] == "logs" {
|
|
||||||
return []byte("nginx: [emerg] host not found in upstream \"c1\" in /etc/nginx/conf.d/openflare_routes.conf:30"), nil
|
|
||||||
}
|
|
||||||
return []byte("false"), nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
executor := &DockerExecutor{
|
executor := &PathExecutor{
|
||||||
DockerBinary: "docker",
|
Path: "/usr/local/openresty/nginx/sbin/openresty",
|
||||||
ContainerName: "openflare-openresty",
|
ConfigPath: "/data/etc/nginx/nginx.conf",
|
||||||
Image: "openresty/openresty:alpine",
|
Runner: runner,
|
||||||
MainConfigPath: filepath.Clean("/tmp/nginx.conf"),
|
|
||||||
RouteConfigDir: filepath.Clean("/tmp/routes"),
|
|
||||||
CertDir: filepath.Clean("/tmp/certs"),
|
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
|
||||||
LuaDir: filepath.Clean("/tmp/lua"),
|
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
|
||||||
Runner: runner,
|
|
||||||
}
|
}
|
||||||
if err := executor.CheckHealth(context.Background()); err == nil {
|
|
||||||
t.Fatal("expected CheckHealth to fail when container is not running")
|
|
||||||
} else {
|
|
||||||
text := err.Error()
|
|
||||||
if !strings.Contains(text, "exit_code=1") {
|
|
||||||
t.Fatalf("expected exit code in health error, got %v", err)
|
|
||||||
}
|
|
||||||
if !strings.Contains(text, "host not found in upstream") {
|
|
||||||
t.Fatalf("expected recent docker logs in health error, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareDockerMountSources(t *testing.T) (string, string, string, string) {
|
|
||||||
t.Helper()
|
|
||||||
tempDir := t.TempDir()
|
|
||||||
mainConfigPath := filepath.Join(tempDir, "nginx.conf")
|
|
||||||
routeConfigDir := filepath.Join(tempDir, "conf.d")
|
|
||||||
certDir := filepath.Join(tempDir, "certs")
|
|
||||||
luaDir := filepath.Join(tempDir, "lua")
|
|
||||||
|
|
||||||
if err := os.WriteFile(mainConfigPath, []byte("events {}\nhttp {}\n"), 0o644); err != nil {
|
|
||||||
t.Fatalf("WriteFile failed: %v", err)
|
|
||||||
}
|
|
||||||
for _, dir := range []string{routeConfigDir, certDir, luaDir} {
|
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
||||||
t.Fatalf("MkdirAll failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mainConfigPath, routeConfigDir, certDir, luaDir
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDockerExecutorStartsContainerWhenMissing(t *testing.T) {
|
|
||||||
mainConfigPath, routeConfigDir, certDir, luaDir := prepareDockerMountSources(t)
|
|
||||||
runner := &fakeRunner{
|
|
||||||
runFn: func(name string, args ...string) ([]byte, error) {
|
|
||||||
if len(args) >= 1 && args[0] == "inspect" {
|
|
||||||
return []byte(""), errors.New("not found")
|
|
||||||
}
|
|
||||||
return []byte("ok"), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
executor := &DockerExecutor{
|
|
||||||
DockerBinary: "docker",
|
|
||||||
ContainerName: "openflare-openresty",
|
|
||||||
Image: "openresty/openresty:alpine",
|
|
||||||
MainConfigPath: mainConfigPath,
|
|
||||||
RouteConfigDir: routeConfigDir,
|
|
||||||
CertDir: certDir,
|
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
|
||||||
LuaDir: luaDir,
|
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
|
||||||
Runner: runner,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := executor.Test(context.Background()); err != nil {
|
|
||||||
t.Fatalf("Test failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(runner.calls) != 1 {
|
|
||||||
t.Fatalf("expected 1 call, got %d", len(runner.calls))
|
|
||||||
}
|
|
||||||
if runner.calls[0].args[0] != "run" || runner.calls[0].args[1] != "--rm" {
|
|
||||||
t.Fatalf("expected docker run --rm for test, got %#v", runner.calls[0])
|
|
||||||
}
|
|
||||||
if runner.calls[0].args[len(runner.calls[0].args)-2] != "openresty" {
|
|
||||||
t.Fatalf("expected docker test command to invoke openresty, got %#v", runner.calls[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDockerExecutorStartsStoppedContainer(t *testing.T) {
|
|
||||||
mainConfigPath, routeConfigDir, certDir, luaDir := prepareDockerMountSources(t)
|
|
||||||
inspectCalls := 0
|
|
||||||
runner := &fakeRunner{
|
|
||||||
runFn: func(name string, args ...string) ([]byte, error) {
|
|
||||||
if len(args) >= 2 && args[0] == "inspect" {
|
|
||||||
inspectCalls++
|
|
||||||
if inspectCalls < 3 {
|
|
||||||
return []byte("false"), nil
|
|
||||||
}
|
|
||||||
return []byte("true"), nil
|
|
||||||
}
|
|
||||||
return []byte("ok"), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
executor := &DockerExecutor{
|
|
||||||
DockerBinary: "docker",
|
|
||||||
ContainerName: "openflare-openresty",
|
|
||||||
Image: "openresty/openresty:alpine",
|
|
||||||
MainConfigPath: mainConfigPath,
|
|
||||||
RouteConfigDir: routeConfigDir,
|
|
||||||
CertDir: certDir,
|
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
|
||||||
LuaDir: luaDir,
|
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
|
||||||
Runner: runner,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := executor.Reload(context.Background()); err != nil {
|
if err := executor.Reload(context.Background()); err != nil {
|
||||||
t.Fatalf("Reload failed: %v", err)
|
t.Fatalf("Reload failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(runner.calls) != 5 {
|
|
||||||
t.Fatalf("expected 5 calls, got %d", len(runner.calls))
|
|
||||||
}
|
|
||||||
if runner.calls[0].args[0] != "inspect" {
|
|
||||||
t.Fatalf("expected docker inspect on first call, got %#v", runner.calls[0])
|
|
||||||
}
|
|
||||||
if runner.calls[1].args[0] != "inspect" {
|
|
||||||
t.Fatalf("expected docker inspect on second call, got %#v", runner.calls[1])
|
|
||||||
}
|
|
||||||
if runner.calls[2].args[0] != "rm" {
|
|
||||||
t.Fatalf("expected docker rm on third call, got %#v", runner.calls[2])
|
|
||||||
}
|
|
||||||
if runner.calls[3].args[0] != "run" {
|
|
||||||
t.Fatalf("expected docker run on fourth call, got %#v", runner.calls[3])
|
|
||||||
}
|
|
||||||
if runner.calls[4].args[0] != "inspect" {
|
|
||||||
t.Fatalf("expected docker inspect after run, got %#v", runner.calls[4])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDockerExecutorReloadsRunningContainerInPlace(t *testing.T) {
|
|
||||||
mainConfigPath, routeConfigDir, certDir, luaDir := prepareDockerMountSources(t)
|
|
||||||
runner := &fakeRunner{
|
|
||||||
runFn: func(name string, args ...string) ([]byte, error) {
|
|
||||||
if len(args) >= 1 && args[0] == "inspect" {
|
|
||||||
return []byte("true"), nil
|
|
||||||
}
|
|
||||||
return []byte("ok"), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
executor := &DockerExecutor{
|
|
||||||
DockerBinary: "docker",
|
|
||||||
ContainerName: "openflare-openresty",
|
|
||||||
Image: "openresty/openresty:alpine",
|
|
||||||
MainConfigPath: mainConfigPath,
|
|
||||||
RouteConfigDir: routeConfigDir,
|
|
||||||
CertDir: certDir,
|
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
|
||||||
LuaDir: luaDir,
|
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
|
||||||
Runner: runner,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := executor.Reload(context.Background()); err != nil {
|
|
||||||
t.Fatalf("Reload failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := []runCall{
|
expected := []runCall{
|
||||||
{name: "docker", args: []string{"inspect", "-f", "{{.State.Running}}", "openflare-openresty"}},
|
{name: "/usr/local/openresty/nginx/sbin/openresty", args: []string{"-s", "reload", "-c", "/data/etc/nginx/nginx.conf"}},
|
||||||
{name: "docker", args: []string{"exec", "openflare-openresty", "openresty", "-s", "reload"}},
|
{name: "/usr/local/openresty/nginx/sbin/openresty", args: []string{"-c", "/data/etc/nginx/nginx.conf"}},
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(runner.calls, expected) {
|
if !reflect.DeepEqual(runner.calls, expected) {
|
||||||
t.Fatalf("unexpected calls: %#v", runner.calls)
|
t.Fatalf("unexpected calls: %#v", runner.calls)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDockerExecutorReloadRecreatesContainerWhenMountedCertMissing(t *testing.T) {
|
|
||||||
mainConfigPath, routeConfigDir, certDir, luaDir := prepareDockerMountSources(t)
|
|
||||||
runner := &fakeRunner{
|
|
||||||
runFn: func(name string, args ...string) ([]byte, error) {
|
|
||||||
if len(args) >= 1 && args[0] == "inspect" {
|
|
||||||
return []byte("true"), nil
|
|
||||||
}
|
|
||||||
if len(args) >= 2 && args[0] == "exec" {
|
|
||||||
return []byte(`nginx: [emerg] cannot load certificate "/etc/nginx/openflare-certs/1.crt": BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory)`), errors.New("exit status 1")
|
|
||||||
}
|
|
||||||
return []byte("ok"), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
executor := &DockerExecutor{
|
|
||||||
DockerBinary: "docker",
|
|
||||||
ContainerName: "openflare-openresty",
|
|
||||||
Image: "openresty/openresty:alpine",
|
|
||||||
MainConfigPath: mainConfigPath,
|
|
||||||
RouteConfigDir: routeConfigDir,
|
|
||||||
CertDir: certDir,
|
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
|
||||||
LuaDir: luaDir,
|
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
|
||||||
OpenrestyObservabilityPort: 18081,
|
|
||||||
Runner: runner,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := executor.Reload(context.Background()); err != nil {
|
|
||||||
t.Fatalf("Reload failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(runner.calls) != 6 {
|
|
||||||
t.Fatalf("expected 6 calls, got %d", len(runner.calls))
|
|
||||||
}
|
|
||||||
if runner.calls[2].args[0] != "inspect" || runner.calls[3].args[0] != "rm" || runner.calls[4].args[0] != "run" || runner.calls[5].args[0] != "inspect" {
|
|
||||||
t.Fatalf("expected recreate after reload failure, got %#v", runner.calls)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDockerExecutorRunContainerMountsManagedFiles(t *testing.T) {
|
|
||||||
mainConfigPath, routeConfigDir, certDir, luaDir := prepareDockerMountSources(t)
|
|
||||||
runner := &fakeRunner{
|
|
||||||
runFn: func(name string, args ...string) ([]byte, error) {
|
|
||||||
if len(args) >= 1 && args[0] == "inspect" {
|
|
||||||
return []byte("true"), nil
|
|
||||||
}
|
|
||||||
return []byte("ok"), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
executor := &DockerExecutor{
|
|
||||||
DockerBinary: "docker",
|
|
||||||
ContainerName: "openflare-openresty",
|
|
||||||
Image: "openresty/openresty:alpine",
|
|
||||||
MainConfigPath: mainConfigPath,
|
|
||||||
RouteConfigDir: routeConfigDir,
|
|
||||||
CertDir: certDir,
|
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
|
||||||
LuaDir: luaDir,
|
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
|
||||||
OpenrestyObservabilityPort: 18081,
|
|
||||||
Runner: runner,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := executor.runContainer(context.Background()); err != nil {
|
|
||||||
t.Fatalf("runContainer failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(runner.calls) != 2 {
|
|
||||||
t.Fatalf("expected docker run plus health check, got %d calls", len(runner.calls))
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedArgs := []string{
|
|
||||||
"run", "-d",
|
|
||||||
"--name", "openflare-openresty",
|
|
||||||
"--restart", "always",
|
|
||||||
"-p", "80:80",
|
|
||||||
"-p", "443:443",
|
|
||||||
"-p", "127.0.0.1:18081:18081",
|
|
||||||
"-v", mainConfigPath + ":" + DockerMainConfigPath,
|
|
||||||
"-v", routeConfigDir + ":/etc/nginx/conf.d",
|
|
||||||
"-v", certDir + ":/etc/nginx/openflare-certs",
|
|
||||||
"-v", luaDir + ":/etc/nginx/openflare-lua",
|
|
||||||
"openresty/openresty:alpine",
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(runner.calls[0].args, expectedArgs) {
|
|
||||||
t.Fatalf("unexpected docker run args: %#v", runner.calls[0].args)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(runner.calls[1].args, []string{"inspect", "-f", "{{.State.Running}}", "openflare-openresty"}) {
|
|
||||||
t.Fatalf("unexpected docker health check args: %#v", runner.calls[1].args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDockerExecutorRecreatesContainerOnStartup(t *testing.T) {
|
|
||||||
mainConfigPath, routeConfigDir, certDir, luaDir := prepareDockerMountSources(t)
|
|
||||||
runner := &fakeRunner{
|
|
||||||
runFn: func(name string, args ...string) ([]byte, error) {
|
|
||||||
if len(args) >= 1 && args[0] == "inspect" {
|
|
||||||
return []byte("true"), nil
|
|
||||||
}
|
|
||||||
return []byte("ok"), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
executor := &DockerExecutor{
|
|
||||||
DockerBinary: "docker",
|
|
||||||
ContainerName: "openflare-openresty",
|
|
||||||
Image: "openresty/openresty:alpine",
|
|
||||||
MainConfigPath: mainConfigPath,
|
|
||||||
RouteConfigDir: routeConfigDir,
|
|
||||||
CertDir: certDir,
|
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
|
||||||
LuaDir: luaDir,
|
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
|
||||||
OpenrestyObservabilityPort: 18081,
|
|
||||||
Runner: runner,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := executor.EnsureRuntime(context.Background(), true); err != nil {
|
|
||||||
t.Fatalf("EnsureRuntime failed: %v", err)
|
|
||||||
}
|
|
||||||
if len(runner.calls) != 4 {
|
|
||||||
t.Fatalf("expected 4 calls, got %d", len(runner.calls))
|
|
||||||
}
|
|
||||||
if runner.calls[1].args[0] != "rm" {
|
|
||||||
t.Fatalf("expected docker rm on second call, got %#v", runner.calls[1])
|
|
||||||
}
|
|
||||||
if runner.calls[2].args[0] != "run" {
|
|
||||||
t.Fatalf("expected docker run on third call, got %#v", runner.calls[2])
|
|
||||||
}
|
|
||||||
if runner.calls[3].args[0] != "inspect" {
|
|
||||||
t.Fatalf("expected docker inspect after run, got %#v", runner.calls[3])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDockerExecutorRunContainerRejectsMissingMainConfigFile(t *testing.T) {
|
|
||||||
tempDir := t.TempDir()
|
|
||||||
routeConfigDir := filepath.Join(tempDir, "conf.d")
|
|
||||||
certDir := filepath.Join(tempDir, "certs")
|
|
||||||
luaDir := filepath.Join(tempDir, "lua")
|
|
||||||
for _, dir := range []string{routeConfigDir, certDir, luaDir} {
|
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
||||||
t.Fatalf("MkdirAll failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executor := &DockerExecutor{
|
|
||||||
DockerBinary: "docker",
|
|
||||||
ContainerName: "openflare-openresty",
|
|
||||||
Image: "openresty/openresty:alpine",
|
|
||||||
MainConfigPath: filepath.Join(tempDir, "nginx.conf"),
|
|
||||||
RouteConfigDir: routeConfigDir,
|
|
||||||
CertDir: certDir,
|
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
|
||||||
LuaDir: luaDir,
|
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
|
||||||
Runner: &fakeRunner{},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := executor.runContainer(context.Background())
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected missing main config file to be rejected")
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), "run a config apply first") {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDockerExecutorRunContainerRejectsMainConfigDirectory(t *testing.T) {
|
|
||||||
tempDir := t.TempDir()
|
|
||||||
mainConfigPath := filepath.Join(tempDir, "nginx.conf")
|
|
||||||
routeConfigDir := filepath.Join(tempDir, "conf.d")
|
|
||||||
certDir := filepath.Join(tempDir, "certs")
|
|
||||||
luaDir := filepath.Join(tempDir, "lua")
|
|
||||||
for _, dir := range []string{mainConfigPath, routeConfigDir, certDir, luaDir} {
|
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
||||||
t.Fatalf("MkdirAll failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executor := &DockerExecutor{
|
|
||||||
DockerBinary: "docker",
|
|
||||||
ContainerName: "openflare-openresty",
|
|
||||||
Image: "openresty/openresty:alpine",
|
|
||||||
MainConfigPath: mainConfigPath,
|
|
||||||
RouteConfigDir: routeConfigDir,
|
|
||||||
CertDir: certDir,
|
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
|
||||||
LuaDir: luaDir,
|
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
|
||||||
Runner: &fakeRunner{},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := executor.runContainer(context.Background())
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected main config directory to be rejected")
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), "expected a file") {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewExecutorUsesAbsoluteDockerMountPath(t *testing.T) {
|
|
||||||
executor := NewExecutor(ExecutorOptions{
|
|
||||||
DockerBinary: "docker",
|
|
||||||
ContainerName: "openflare-openresty",
|
|
||||||
Image: "openresty/openresty:alpine",
|
|
||||||
MainConfigPath: "./data/etc/nginx/nginx.conf",
|
|
||||||
RouteConfigPath: "./data/etc/nginx/conf.d/openflare_routes.conf",
|
|
||||||
CertDir: "./data/etc/nginx/certs",
|
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
|
||||||
LuaDir: "./data/etc/nginx/lua",
|
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
|
||||||
OpenrestyObservabilityPort: 18081,
|
|
||||||
})
|
|
||||||
|
|
||||||
dockerExecutor, ok := executor.(*DockerExecutor)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("expected docker executor")
|
|
||||||
}
|
|
||||||
if !filepath.IsAbs(dockerExecutor.RouteConfigDir) {
|
|
||||||
t.Fatalf("expected absolute route config dir, got %s", dockerExecutor.RouteConfigDir)
|
|
||||||
}
|
|
||||||
if !filepath.IsAbs(dockerExecutor.MainConfigPath) {
|
|
||||||
t.Fatalf("expected absolute main config path, got %s", dockerExecutor.MainConfigPath)
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(dockerExecutor.RouteConfigDir, filepath.Clean("data/etc/nginx/conf.d")) {
|
|
||||||
t.Fatalf("unexpected route config dir: %s", dockerExecutor.RouteConfigDir)
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(dockerExecutor.MainConfigPath, filepath.Clean("data/etc/nginx/nginx.conf")) {
|
|
||||||
t.Fatalf("unexpected main config path: %s", dockerExecutor.MainConfigPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDetectVersionFromBinary(t *testing.T) {
|
func TestDetectVersionFromBinary(t *testing.T) {
|
||||||
version, err := detectVersion(context.Background(), ExecutorOptions{
|
version, err := detectVersion(context.Background(), ExecutorOptions{
|
||||||
NginxPath: "/usr/local/openresty/nginx/sbin/openresty",
|
NginxPath: "/usr/local/openresty/nginx/sbin/openresty",
|
||||||
@@ -578,9 +194,11 @@ func TestManagerApplyAndChecksumIncludeMainConfig(t *testing.T) {
|
|||||||
mainPath := filepath.Join(tempDir, "nginx.conf")
|
mainPath := filepath.Join(tempDir, "nginx.conf")
|
||||||
routePath := filepath.Join(tempDir, "conf.d", "openflare_routes.conf")
|
routePath := filepath.Join(tempDir, "conf.d", "openflare_routes.conf")
|
||||||
certDir := filepath.Join(tempDir, "certs")
|
certDir := filepath.Join(tempDir, "certs")
|
||||||
|
accessLogPath := filepath.Join(tempDir, "var", "log", "openflare", "access.log")
|
||||||
manager := &Manager{
|
manager := &Manager{
|
||||||
MainConfigPath: mainPath,
|
MainConfigPath: mainPath,
|
||||||
RouteConfigPath: routePath,
|
RouteConfigPath: routePath,
|
||||||
|
AccessLogPath: accessLogPath,
|
||||||
CertDir: certDir,
|
CertDir: certDir,
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
NginxCertDir: "/etc/nginx/openflare-certs",
|
||||||
LuaDir: filepath.Join(tempDir, "lua"),
|
LuaDir: filepath.Join(tempDir, "lua"),
|
||||||
@@ -602,7 +220,7 @@ func TestManagerApplyAndChecksumIncludeMainConfig(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to read main config: %v", err)
|
t.Fatalf("failed to read main config: %v", err)
|
||||||
}
|
}
|
||||||
expectedMain := "include " + routePath + ";\naccess_log " + filepath.ToSlash(filepath.Join(filepath.Dir(routePath), "openflare_access.log")) + " openflare_json;\n"
|
expectedMain := "include " + routePath + ";\naccess_log " + filepath.ToSlash(accessLogPath) + " openflare_json;\n"
|
||||||
if string(mainData) != expectedMain {
|
if string(mainData) != expectedMain {
|
||||||
t.Fatalf("unexpected main config: %s", string(mainData))
|
t.Fatalf("unexpected main config: %s", string(mainData))
|
||||||
}
|
}
|
||||||
@@ -629,73 +247,6 @@ func TestManagerApplyAndChecksumIncludeMainConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManagerApplyUsesRuntimeRouteConfigPath(t *testing.T) {
|
|
||||||
tempDir := t.TempDir()
|
|
||||||
mainPath := filepath.Join(tempDir, "nginx.conf")
|
|
||||||
routePath := filepath.Join(tempDir, "conf.d", "openflare_routes.conf")
|
|
||||||
manager := &Manager{
|
|
||||||
MainConfigPath: mainPath,
|
|
||||||
RouteConfigPath: routePath,
|
|
||||||
RuntimeRouteConfigPath: DockerRouteConfigPath,
|
|
||||||
CertDir: filepath.Join(tempDir, "certs"),
|
|
||||||
NginxCertDir: "/etc/nginx/openflare-certs",
|
|
||||||
LuaDir: filepath.Join(tempDir, "lua"),
|
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
|
||||||
Executor: &fakeExecutor{},
|
|
||||||
}
|
|
||||||
|
|
||||||
if outcome := manager.Apply(context.Background(), "include __OPENFLARE_ROUTE_CONFIG__;\naccess_log __OPENFLARE_ACCESS_LOG__ openflare_json;\n", "server { listen 80; }\n", nil); outcome.Status != ApplyStatusSuccess {
|
|
||||||
t.Fatalf("Apply failed: %#v", outcome)
|
|
||||||
}
|
|
||||||
|
|
||||||
mainData, err := os.ReadFile(mainPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to read main config: %v", err)
|
|
||||||
}
|
|
||||||
expectedMain := "include " + DockerRouteConfigPath + ";\naccess_log " + DockerAccessLogPath + " openflare_json;\n"
|
|
||||||
if string(mainData) != expectedMain {
|
|
||||||
t.Fatalf("unexpected main config include path: %s", string(mainData))
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := manager.CurrentChecksum()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CurrentChecksum failed: %v", err)
|
|
||||||
}
|
|
||||||
expected := bundleChecksum(
|
|
||||||
"include __OPENFLARE_ROUTE_CONFIG__;\naccess_log __OPENFLARE_ACCESS_LOG__ openflare_json;\n",
|
|
||||||
"server { listen 80; }\n",
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if value != expected {
|
|
||||||
t.Fatalf("unexpected checksum: got %s want %s", value, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDetectVersionFromDockerImage(t *testing.T) {
|
|
||||||
runner := &fakeRunner{
|
|
||||||
runFn: func(name string, args ...string) ([]byte, error) {
|
|
||||||
return []byte("nginx version: openresty/1.27.1.2\n"), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
version, err := detectVersion(context.Background(), ExecutorOptions{
|
|
||||||
DockerBinary: "docker",
|
|
||||||
Image: "openresty/openresty:alpine",
|
|
||||||
}, runner)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("detectVersion failed: %v", err)
|
|
||||||
}
|
|
||||||
if version != "1.27.1.2" {
|
|
||||||
t.Fatalf("unexpected version: %s", version)
|
|
||||||
}
|
|
||||||
if len(runner.calls) != 1 {
|
|
||||||
t.Fatalf("expected one command call, got %d", len(runner.calls))
|
|
||||||
}
|
|
||||||
expectedArgs := []string{"run", "--rm", "openresty/openresty:alpine", "openresty", "-v"}
|
|
||||||
if !reflect.DeepEqual(runner.calls[0].args, expectedArgs) {
|
|
||||||
t.Fatalf("unexpected docker args: %#v", runner.calls[0].args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseNginxVersionIgnoresDockerEntrypointPaths(t *testing.T) {
|
func TestParseNginxVersionIgnoresDockerEntrypointPaths(t *testing.T) {
|
||||||
output := strings.Join([]string{
|
output := strings.Join([]string{
|
||||||
"/docker-entrypoint.sh: /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh: info: can not modify /etc/nginx/conf.d/default.conf (read-only file system?)",
|
"/docker-entrypoint.sh: /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh: info: can not modify /etc/nginx/conf.d/default.conf (read-only file system?)",
|
||||||
@@ -904,8 +455,9 @@ func TestCertFileMode(t *testing.T) {
|
|||||||
func TestManagerEnsureLuaAssetsWritesReadableFiles(t *testing.T) {
|
func TestManagerEnsureLuaAssetsWritesReadableFiles(t *testing.T) {
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
manager := &Manager{
|
manager := &Manager{
|
||||||
LuaDir: filepath.Join(tempDir, "lua"),
|
LuaDir: filepath.Join(tempDir, "lua"),
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
NginxLuaDir: "/etc/nginx/openflare-lua",
|
||||||
|
RuntimeConfigDir: filepath.Join(tempDir, "runtime"),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := manager.EnsureLuaAssets()
|
err := manager.EnsureLuaAssets()
|
||||||
@@ -923,20 +475,28 @@ func TestManagerEnsureLuaAssetsWritesReadableFiles(t *testing.T) {
|
|||||||
if _, err := os.Stat(filepath.Join(manager.LuaDir, "pow", "check.lua")); err != nil {
|
if _, err := os.Stat(filepath.Join(manager.LuaDir, "pow", "check.lua")); err != nil {
|
||||||
t.Fatalf("failed to stat pow lua file: %v", err)
|
t.Fatalf("failed to stat pow lua file: %v", err)
|
||||||
}
|
}
|
||||||
|
data, err := os.ReadFile(filepath.Join(manager.LuaDir, "pow", "check.lua"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read pow lua file: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(data), filepath.ToSlash(manager.RuntimeConfigDir)+"/pow_config.json") {
|
||||||
|
t.Fatalf("expected pow lua to read runtime config dir, got %s", string(data))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnsureLuaAssetsPreservesPowConfig(t *testing.T) {
|
func TestEnsureLuaAssetsLeavesRuntimePowConfigOutsideLuaDir(t *testing.T) {
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
luaDir := filepath.Join(tempDir, "lua")
|
luaDir := filepath.Join(tempDir, "lua")
|
||||||
if err := os.MkdirAll(luaDir, 0o755); err != nil {
|
runtimeConfigDir := filepath.Join(tempDir, "runtime")
|
||||||
|
if err := os.MkdirAll(runtimeConfigDir, 0o755); err != nil {
|
||||||
t.Fatalf("MkdirAll failed: %v", err)
|
t.Fatalf("MkdirAll failed: %v", err)
|
||||||
}
|
}
|
||||||
powConfigPath := filepath.Join(luaDir, "pow_config.json")
|
powConfigPath := filepath.Join(runtimeConfigDir, "pow_config.json")
|
||||||
want := `[{"domains":["pow.example.com"],"enabled":true}]`
|
want := `[{"domains":["pow.example.com"],"enabled":true}]`
|
||||||
if err := os.WriteFile(powConfigPath, []byte(want), 0o644); err != nil {
|
if err := os.WriteFile(powConfigPath, []byte(want), 0o644); err != nil {
|
||||||
t.Fatalf("WriteFile failed: %v", err)
|
t.Fatalf("WriteFile failed: %v", err)
|
||||||
}
|
}
|
||||||
manager := &Manager{LuaDir: luaDir}
|
manager := &Manager{LuaDir: luaDir, RuntimeConfigDir: runtimeConfigDir}
|
||||||
|
|
||||||
if err := manager.EnsureLuaAssets(); err != nil {
|
if err := manager.EnsureLuaAssets(); err != nil {
|
||||||
t.Fatalf("EnsureLuaAssets failed: %v", err)
|
t.Fatalf("EnsureLuaAssets failed: %v", err)
|
||||||
@@ -949,6 +509,52 @@ func TestEnsureLuaAssetsPreservesPowConfig(t *testing.T) {
|
|||||||
if string(got) != want {
|
if string(got) != want {
|
||||||
t.Fatalf("unexpected pow_config.json content: got %s want %s", string(got), want)
|
t.Fatalf("unexpected pow_config.json content: got %s want %s", string(got), want)
|
||||||
}
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(luaDir, "pow_config.json")); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("expected lua pow_config.json to stay absent, stat err = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagerApplyWritesPowConfigToRuntimeDirAndCleansLegacyCopies(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
certDir := filepath.Join(tempDir, "certs")
|
||||||
|
luaDir := filepath.Join(tempDir, "lua")
|
||||||
|
runtimeConfigDir := filepath.Join(tempDir, "runtime")
|
||||||
|
for _, dir := range []string{certDir, luaDir, runtimeConfigDir} {
|
||||||
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||||
|
t.Fatalf("MkdirAll failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, path := range []string{filepath.Join(certDir, "pow_config.json"), filepath.Join(luaDir, "pow_config.json")} {
|
||||||
|
if err := os.WriteFile(path, []byte("stale"), 0o644); err != nil {
|
||||||
|
t.Fatalf("WriteFile failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
manager := &Manager{
|
||||||
|
MainConfigPath: filepath.Join(tempDir, "nginx.conf"),
|
||||||
|
RouteConfigPath: filepath.Join(tempDir, "routes.conf"),
|
||||||
|
CertDir: certDir,
|
||||||
|
LuaDir: luaDir,
|
||||||
|
RuntimeConfigDir: runtimeConfigDir,
|
||||||
|
Executor: &fakeExecutor{},
|
||||||
|
}
|
||||||
|
outcome := manager.Apply(context.Background(), "main", "route", []protocol.SupportFile{
|
||||||
|
{Path: "pow_config.json", Content: "runtime"},
|
||||||
|
})
|
||||||
|
if outcome.Status != ApplyStatusSuccess {
|
||||||
|
t.Fatalf("Apply failed: %#v", outcome)
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(filepath.Join(runtimeConfigDir, "pow_config.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read runtime pow config: %v", err)
|
||||||
|
}
|
||||||
|
if string(data) != "runtime" {
|
||||||
|
t.Fatalf("unexpected runtime pow config: %s", string(data))
|
||||||
|
}
|
||||||
|
for _, path := range []string{filepath.Join(certDir, "pow_config.json"), filepath.Join(luaDir, "pow_config.json")} {
|
||||||
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("expected legacy pow config to be removed from %s, stat err = %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManagerCurrentChecksumIncludesPowConfig(t *testing.T) {
|
func TestManagerCurrentChecksumIncludesPowConfig(t *testing.T) {
|
||||||
@@ -956,12 +562,14 @@ func TestManagerCurrentChecksumIncludesPowConfig(t *testing.T) {
|
|||||||
mainPath := filepath.Join(tempDir, "nginx.conf")
|
mainPath := filepath.Join(tempDir, "nginx.conf")
|
||||||
routePath := filepath.Join(tempDir, "routes.conf")
|
routePath := filepath.Join(tempDir, "routes.conf")
|
||||||
luaDir := filepath.Join(tempDir, "lua")
|
luaDir := filepath.Join(tempDir, "lua")
|
||||||
|
runtimeConfigDir := filepath.Join(tempDir, "runtime")
|
||||||
manager := &Manager{
|
manager := &Manager{
|
||||||
MainConfigPath: mainPath,
|
MainConfigPath: mainPath,
|
||||||
RouteConfigPath: routePath,
|
RouteConfigPath: routePath,
|
||||||
LuaDir: luaDir,
|
LuaDir: luaDir,
|
||||||
NginxLuaDir: "/etc/nginx/openflare-lua",
|
NginxLuaDir: "/etc/nginx/openflare-lua",
|
||||||
Executor: &fakeExecutor{},
|
RuntimeConfigDir: runtimeConfigDir,
|
||||||
|
Executor: &fakeExecutor{},
|
||||||
}
|
}
|
||||||
|
|
||||||
outcome := manager.Apply(
|
outcome := manager.Apply(
|
||||||
@@ -1189,8 +797,8 @@ func TestManagerApplyRejectsCertFilePathTraversal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestObservabilityListenAddress(t *testing.T) {
|
func TestObservabilityListenAddress(t *testing.T) {
|
||||||
if got := ObservabilityListenAddress("", 18081); got != "18081" {
|
if got := ObservabilityListenAddress("", 18081); got != "127.0.0.1:18081" {
|
||||||
t.Fatalf("unexpected docker observability listen address: %s", got)
|
t.Fatalf("unexpected default observability listen address: %s", got)
|
||||||
}
|
}
|
||||||
if got := ObservabilityListenAddress("/usr/local/openresty/nginx/sbin/openresty", 18081); got != "127.0.0.1:18081" {
|
if got := ObservabilityListenAddress("/usr/local/openresty/nginx/sbin/openresty", 18081); got != "127.0.0.1:18081" {
|
||||||
t.Fatalf("unexpected path observability listen address: %s", got)
|
t.Fatalf("unexpected path observability listen address: %s", got)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ end
|
|||||||
-- Lazy-load pow_config from file; reload when content changes
|
-- Lazy-load pow_config from file; reload when content changes
|
||||||
local function load_pow_config()
|
local function load_pow_config()
|
||||||
local config_paths = {
|
local config_paths = {
|
||||||
ngx.config.prefix() .. "openflare-lua/pow_config.json",
|
"__OPENFLARE_RUNTIME_CONFIG_DIR__/pow_config.json",
|
||||||
"/etc/nginx/openflare-lua/pow_config.json",
|
"/etc/nginx/openflare-lua/pow_config.json",
|
||||||
"/usr/local/openresty/nginx/conf/pow_config.json"
|
"/usr/local/openresty/nginx/conf/pow_config.json"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"openflare-agent/internal/protocol"
|
"openflare-agent/internal/protocol"
|
||||||
"openflare-agent/internal/state"
|
"openflare-agent/internal/state"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -128,10 +127,10 @@ func readAccessLogDelta(cfg *config.Config, stateStore *state.Store) *trafficAgg
|
|||||||
}
|
}
|
||||||
|
|
||||||
func managedAccessLogPath(cfg *config.Config) string {
|
func managedAccessLogPath(cfg *config.Config) string {
|
||||||
if cfg == nil || strings.TrimSpace(cfg.RouteConfigPath) == "" {
|
if cfg == nil || strings.TrimSpace(cfg.AccessLogPath) == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return filepath.Join(filepath.Dir(cfg.RouteConfigPath), "openflare_access.log")
|
return cfg.AccessLogPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTrafficAggregate() *trafficAggregate {
|
func newTrafficAggregate() *trafficAggregate {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func TestBuildTrafficReportAggregatesManagedAccessLog(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stateStore := state.NewStore(filepath.Join(tempDir, "state.json"))
|
stateStore := state.NewStore(filepath.Join(tempDir, "state.json"))
|
||||||
report := BuildTrafficReport(&config.Config{RouteConfigPath: routeConfigPath}, stateStore, nil)
|
report := BuildTrafficReport(&config.Config{AccessLogPath: logPath}, stateStore, nil)
|
||||||
if report == nil {
|
if report == nil {
|
||||||
t.Fatal("expected traffic report")
|
t.Fatal("expected traffic report")
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,7 @@ func TestBuildTrafficReportAggregatesManagedAccessLog(t *testing.T) {
|
|||||||
t.Fatalf("unexpected access log offset: %d", snapshot.AccessLogOffset)
|
t.Fatalf("unexpected access log offset: %d", snapshot.AccessLogOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
secondReport := BuildTrafficReport(&config.Config{RouteConfigPath: routeConfigPath}, stateStore, nil)
|
secondReport := BuildTrafficReport(&config.Config{AccessLogPath: logPath}, stateStore, nil)
|
||||||
if secondReport != nil {
|
if secondReport != nil {
|
||||||
t.Fatalf("expected no report without appended lines, got %+v", secondReport)
|
t.Fatalf("expected no report without appended lines, got %+v", secondReport)
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ func TestBuildTrafficReportResetsOffsetAfterTruncate(t *testing.T) {
|
|||||||
t.Fatalf("Save failed: %v", err)
|
t.Fatalf("Save failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
report := BuildTrafficReport(&config.Config{RouteConfigPath: routeConfigPath}, stateStore, nil)
|
report := BuildTrafficReport(&config.Config{AccessLogPath: logPath}, stateStore, nil)
|
||||||
if report == nil || report.RequestCount != 1 {
|
if report == nil || report.RequestCount != 1 {
|
||||||
t.Fatalf("expected one request after truncate reset, got %+v", report)
|
t.Fatalf("expected one request after truncate reset, got %+v", report)
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ func TestBuildTrafficObservabilityReturnsAccessLogs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stateStore := state.NewStore(filepath.Join(tempDir, "state.json"))
|
stateStore := state.NewStore(filepath.Join(tempDir, "state.json"))
|
||||||
report, accessLogs, fallbackMetrics := BuildTrafficObservability(&config.Config{RouteConfigPath: routeConfigPath}, stateStore, nil)
|
report, accessLogs, fallbackMetrics := BuildTrafficObservability(&config.Config{AccessLogPath: logPath}, stateStore, nil)
|
||||||
if report == nil || report.RequestCount != 2 {
|
if report == nil || report.RequestCount != 2 {
|
||||||
t.Fatalf("expected traffic report, got %+v", report)
|
t.Fatalf("expected traffic report, got %+v", report)
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ func TestBuildTrafficObservabilityTruncatesLongAccessLogPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stateStore := state.NewStore(filepath.Join(tempDir, "state.json"))
|
stateStore := state.NewStore(filepath.Join(tempDir, "state.json"))
|
||||||
_, accessLogs, _ := BuildTrafficObservability(&config.Config{RouteConfigPath: routeConfigPath}, stateStore, nil)
|
_, accessLogs, _ := BuildTrafficObservability(&config.Config{AccessLogPath: logPath}, stateStore, nil)
|
||||||
if len(accessLogs) != 1 {
|
if len(accessLogs) != 1 {
|
||||||
t.Fatalf("expected one access log, got %+v", accessLogs)
|
t.Fatalf("expected one access log, got %+v", accessLogs)
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ func TestBuildTrafficReportParsesCombinedAccessLog(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stateStore := state.NewStore(filepath.Join(tempDir, "state.json"))
|
stateStore := state.NewStore(filepath.Join(tempDir, "state.json"))
|
||||||
report := BuildTrafficReport(&config.Config{RouteConfigPath: routeConfigPath}, stateStore, nil)
|
report := BuildTrafficReport(&config.Config{AccessLogPath: logPath}, stateStore, nil)
|
||||||
if report == nil {
|
if report == nil {
|
||||||
t.Fatal("expected traffic report from combined access log")
|
t.Fatal("expected traffic report from combined access log")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ DISCOVERY_TOKEN=""
|
|||||||
AGENT_TOKEN=""
|
AGENT_TOKEN=""
|
||||||
CREATE_SERVICE="true"
|
CREATE_SERVICE="true"
|
||||||
SERVICE_NAME="openflare-agent"
|
SERVICE_NAME="openflare-agent"
|
||||||
|
OPENRESTY_PATH=""
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
@@ -27,6 +28,7 @@ Options:
|
|||||||
--discovery-token TOKEN Discovery token for auto-registration
|
--discovery-token TOKEN Discovery token for auto-registration
|
||||||
--agent-token TOKEN Node-specific agent token
|
--agent-token TOKEN Node-specific agent token
|
||||||
--install-dir DIR Installation directory (default: /opt/openflare-agent)
|
--install-dir DIR Installation directory (default: /opt/openflare-agent)
|
||||||
|
--openresty-path PATH OpenResty binary path (default: auto-detect from PATH)
|
||||||
--repo REPO GitHub repository (default: Rain-kl/OpenFlare)
|
--repo REPO GitHub repository (default: Rain-kl/OpenFlare)
|
||||||
--no-service Do not create systemd service
|
--no-service Do not create systemd service
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
@@ -51,6 +53,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--discovery-token) DISCOVERY_TOKEN="$2"; shift 2 ;;
|
--discovery-token) DISCOVERY_TOKEN="$2"; shift 2 ;;
|
||||||
--agent-token) AGENT_TOKEN="$2"; shift 2 ;;
|
--agent-token) AGENT_TOKEN="$2"; shift 2 ;;
|
||||||
--install-dir) INSTALL_DIR="$2"; shift 2 ;;
|
--install-dir) INSTALL_DIR="$2"; shift 2 ;;
|
||||||
|
--openresty-path) OPENRESTY_PATH="$2"; shift 2 ;;
|
||||||
--repo) REPO="$2"; shift 2 ;;
|
--repo) REPO="$2"; shift 2 ;;
|
||||||
--no-service) CREATE_SERVICE="false"; shift ;;
|
--no-service) CREATE_SERVICE="false"; shift ;;
|
||||||
-h|--help) usage ;;
|
-h|--help) usage ;;
|
||||||
@@ -82,6 +85,20 @@ if [[ "$OS" != "linux" && "$OS" != "darwin" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$OPENRESTY_PATH" ]]; then
|
||||||
|
if command -v openresty >/dev/null 2>&1; then
|
||||||
|
OPENRESTY_PATH="$(command -v openresty)"
|
||||||
|
else
|
||||||
|
echo "Error: openresty was not found in PATH. Install OpenResty first or pass --openresty-path."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -x "$OPENRESTY_PATH" ]]; then
|
||||||
|
echo "Error: OpenResty binary is not executable: ${OPENRESTY_PATH}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
ASSET_NAME="openflare-agent-${OS}-${ARCH}"
|
ASSET_NAME="openflare-agent-${OS}-${ARCH}"
|
||||||
echo "Detected platform: ${OS}/${ARCH}"
|
echo "Detected platform: ${OS}/${ARCH}"
|
||||||
|
|
||||||
@@ -140,9 +157,9 @@ if [[ -n "$AGENT_TOKEN" ]]; then
|
|||||||
{
|
{
|
||||||
"server_url": "${SERVER_URL}",
|
"server_url": "${SERVER_URL}",
|
||||||
"agent_token": "${AGENT_TOKEN}",
|
"agent_token": "${AGENT_TOKEN}",
|
||||||
|
"openresty_path": "${OPENRESTY_PATH}",
|
||||||
"data_dir": "${INSTALL_DIR}/data",
|
"data_dir": "${INSTALL_DIR}/data",
|
||||||
"heartbeat_interval": 30000,
|
"heartbeat_interval": 30000,
|
||||||
"sync_interval": 30000,
|
|
||||||
"request_timeout": 10000
|
"request_timeout": 10000
|
||||||
}
|
}
|
||||||
CFGEOF
|
CFGEOF
|
||||||
@@ -151,9 +168,9 @@ else
|
|||||||
{
|
{
|
||||||
"server_url": "${SERVER_URL}",
|
"server_url": "${SERVER_URL}",
|
||||||
"discovery_token": "${DISCOVERY_TOKEN}",
|
"discovery_token": "${DISCOVERY_TOKEN}",
|
||||||
|
"openresty_path": "${OPENRESTY_PATH}",
|
||||||
"data_dir": "${INSTALL_DIR}/data",
|
"data_dir": "${INSTALL_DIR}/data",
|
||||||
"heartbeat_interval": 30000,
|
"heartbeat_interval": 30000,
|
||||||
"sync_interval": 30000,
|
|
||||||
"request_timeout": 10000
|
"request_timeout": 10000
|
||||||
}
|
}
|
||||||
CFGEOF
|
CFGEOF
|
||||||
@@ -197,3 +214,4 @@ echo "OpenFlare Agent installed successfully!"
|
|||||||
echo " Binary: ${INSTALL_DIR}/openflare-agent"
|
echo " Binary: ${INSTALL_DIR}/openflare-agent"
|
||||||
echo " Config: ${CONFIG_FILE}"
|
echo " Config: ${CONFIG_FILE}"
|
||||||
echo " Data: ${INSTALL_DIR}/data"
|
echo " Data: ${INSTALL_DIR}/data"
|
||||||
|
echo " OpenResty: ${OPENRESTY_PATH}"
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ Options:
|
|||||||
Behavior:
|
Behavior:
|
||||||
1. Stop the agent service/process and remove the entire installation directory
|
1. Stop the agent service/process and remove the entire installation directory
|
||||||
2. Remove the systemd service definition when present
|
2. Remove the systemd service definition when present
|
||||||
3. Check the saved agent config to identify the OpenResty mode
|
3. Leave the local OpenResty installation untouched
|
||||||
4. If Docker mode was used, remove the OpenResty container and try to remove its image
|
|
||||||
5. If local openresty_path mode was used, do not modify the local OpenResty install
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
uninstall-agent.sh
|
uninstall-agent.sh
|
||||||
@@ -44,52 +42,9 @@ if [[ -z "$INSTALL_DIR" || "$INSTALL_DIR" == "/" || "$INSTALL_DIR" == "." ]]; th
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
json_get_string() {
|
|
||||||
local file="$1"
|
|
||||||
local key="$2"
|
|
||||||
local match
|
|
||||||
|
|
||||||
match=$(grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$file" 2>/dev/null | head -n 1 || true)
|
|
||||||
if [[ -z "$match" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf '%s\n' "$match" | sed -E 's/.*:[[:space:]]*"([^"]*)"/\1/'
|
|
||||||
}
|
|
||||||
|
|
||||||
AGENT_BINARY="${INSTALL_DIR}/openflare-agent"
|
AGENT_BINARY="${INSTALL_DIR}/openflare-agent"
|
||||||
CONFIG_FILE="${INSTALL_DIR}/agent.json"
|
|
||||||
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
|
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
|
||||||
|
|
||||||
OPENRESTY_PATH=""
|
|
||||||
OPENRESTY_CONTAINER_NAME="openflare-openresty"
|
|
||||||
OPENRESTY_DOCKER_IMAGE="openresty/openresty:alpine"
|
|
||||||
DOCKER_BINARY="docker"
|
|
||||||
OPENRESTY_MODE="unknown"
|
|
||||||
|
|
||||||
if [[ -f "$CONFIG_FILE" ]]; then
|
|
||||||
OPENRESTY_PATH="$(json_get_string "$CONFIG_FILE" "openresty_path")"
|
|
||||||
OPENRESTY_CONTAINER_NAME="$(json_get_string "$CONFIG_FILE" "openresty_container_name")"
|
|
||||||
OPENRESTY_DOCKER_IMAGE="$(json_get_string "$CONFIG_FILE" "openresty_docker_image")"
|
|
||||||
DOCKER_BINARY="$(json_get_string "$CONFIG_FILE" "docker_binary")"
|
|
||||||
|
|
||||||
if [[ -z "$OPENRESTY_CONTAINER_NAME" ]]; then
|
|
||||||
OPENRESTY_CONTAINER_NAME="openflare-openresty"
|
|
||||||
fi
|
|
||||||
if [[ -z "$OPENRESTY_DOCKER_IMAGE" ]]; then
|
|
||||||
OPENRESTY_DOCKER_IMAGE="openresty/openresty:alpine"
|
|
||||||
fi
|
|
||||||
if [[ -z "$DOCKER_BINARY" ]]; then
|
|
||||||
DOCKER_BINARY="docker"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$OPENRESTY_PATH" ]]; then
|
|
||||||
OPENRESTY_MODE="local"
|
|
||||||
else
|
|
||||||
OPENRESTY_MODE="docker"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
SYSTEMCTL_AVAILABLE="false"
|
SYSTEMCTL_AVAILABLE="false"
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
SYSTEMCTL_AVAILABLE="true"
|
SYSTEMCTL_AVAILABLE="true"
|
||||||
@@ -143,50 +98,5 @@ fi
|
|||||||
|
|
||||||
echo "Agent uninstall complete."
|
echo "Agent uninstall complete."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Checking OpenResty installation mode..."
|
echo "Local OpenResty was not modified. Remove it manually if you no longer need it."
|
||||||
|
|
||||||
if [[ "$OPENRESTY_MODE" == "docker" ]]; then
|
|
||||||
echo "Detected Docker OpenResty mode."
|
|
||||||
|
|
||||||
if ! command -v "$DOCKER_BINARY" >/dev/null 2>&1; then
|
|
||||||
echo "Docker binary '${DOCKER_BINARY}' was not found."
|
|
||||||
echo "Please remove container '${OPENRESTY_CONTAINER_NAME}' and image '${OPENRESTY_DOCKER_IMAGE}' manually."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if "$DOCKER_BINARY" inspect "$OPENRESTY_CONTAINER_NAME" >/dev/null 2>&1; then
|
|
||||||
if [[ -z "$OPENRESTY_DOCKER_IMAGE" ]]; then
|
|
||||||
OPENRESTY_DOCKER_IMAGE="$("$DOCKER_BINARY" inspect -f '{{.Config.Image}}' "$OPENRESTY_CONTAINER_NAME" 2>/dev/null || true)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Removing Docker container: ${OPENRESTY_CONTAINER_NAME}"
|
|
||||||
"$DOCKER_BINARY" rm -f "$OPENRESTY_CONTAINER_NAME"
|
|
||||||
else
|
|
||||||
echo "Docker container not found, skipping: ${OPENRESTY_CONTAINER_NAME}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$OPENRESTY_DOCKER_IMAGE" ]] && "$DOCKER_BINARY" image inspect "$OPENRESTY_DOCKER_IMAGE" >/dev/null 2>&1; then
|
|
||||||
other_container_ids="$("$DOCKER_BINARY" ps -a --filter "ancestor=${OPENRESTY_DOCKER_IMAGE}" --format '{{.ID}}' 2>/dev/null || true)"
|
|
||||||
if [[ -z "$other_container_ids" ]]; then
|
|
||||||
echo "Removing Docker image: ${OPENRESTY_DOCKER_IMAGE}"
|
|
||||||
if ! "$DOCKER_BINARY" image rm "$OPENRESTY_DOCKER_IMAGE"; then
|
|
||||||
echo "Image removal skipped because Docker reported it is still in use."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Docker image is still used by other containers, skipping image removal: ${OPENRESTY_DOCKER_IMAGE}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Docker OpenResty cleanup complete."
|
|
||||||
elif [[ "$OPENRESTY_MODE" == "local" ]]; then
|
|
||||||
echo "Detected local OpenResty mode via openresty_path:"
|
|
||||||
echo " ${OPENRESTY_PATH}"
|
|
||||||
echo "Agent has been removed, but the local OpenResty installation was not modified."
|
|
||||||
echo "Please uninstall the local OpenResty manually if you no longer need it."
|
|
||||||
else
|
|
||||||
echo "OpenResty mode could not be determined because ${CONFIG_FILE} was not found before uninstall."
|
|
||||||
echo "If you were using Docker OpenResty, please remove its container and image manually if needed."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "OpenFlare Agent uninstall finished."
|
echo "OpenFlare Agent uninstall finished."
|
||||||
|
|||||||
Reference in New Issue
Block a user