Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ server:
# host of the control plane
host: 127.0.0.1
grpcPort: 54789 # control plane grpc port
api:
port: 9090 # nginx-agent http api
# tls options - NOT RECOMMENDED FOR PRODUCTION
tls:
enable: false
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ func loadPlugins(commander client.Commander, binary *core.NginxBinaryType, env *
plugins.NewFileWatcher(loadedConfig, env),
plugins.NewFileWatchThrottle(),
plugins.NewEvents(loadedConfig, env, sdkGRPC.NewMessageMeta(uuid.NewString()), binary),
plugins.NewAgentAPI(loadedConfig, env, binary),
)

if len(loadedConfig.Nginx.NginxCountingSocket) > 0 {
Expand Down
3 changes: 3 additions & 0 deletions nginx-agent.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ server:
# provide servername overrides if using SNI
# metrics: ""
# command: ""
api:
# port to expose http api
port: 9090
# tls options
tls:
# enable tls in the nginx-agent setup for grpcs
Expand Down
9 changes: 8 additions & 1 deletion src/core/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ func GetConfig(clientId string) (*Config, error) {
ClientID: clientId,
CloudAccountID: Viper.GetString(CloudAccountIdKey),
Server: getServer(),
AgentAPI: getAgentAPI(),
ConfigDirs: Viper.GetString(ConfigDirsKey),
Log: getLog(),
TLS: getTLS(),
Expand Down Expand Up @@ -326,13 +327,19 @@ func getNginx() Nginx {
func getServer() Server {
return Server{
Host: Viper.GetString(ServerHost),
GrpcPort: Viper.GetInt(ServerGrpcport),
GrpcPort: Viper.GetInt(ServerGrpcPort),
Token: Viper.GetString(ServerToken),
Metrics: Viper.GetString(ServerMetrics),
Command: Viper.GetString(ServerCommand),
}
}

func getAgentAPI() AgentAPI {
return AgentAPI{
Port: Viper.GetInt(AgentAPIPort),
}
}

func getTLS() TLSConfig {
return TLSConfig{
Enable: Viper.GetBool(TlsEnable),
Expand Down
13 changes: 9 additions & 4 deletions src/core/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import (
"github.com/stretchr/testify/require"

agent_config "github.com/nginx/agent/sdk/v2/agent/config"

sysutils "github.com/nginx/agent/v2/test/utils/system"
)

const (
updatedServerHost = "192.168.0.1"
updatedServerPort = 11000
updatedServerGrpcPort = 11000
updatedAgentAPIPort = 9010
updatedLogLevel = "fatal"
updatedLogPath = "./test-path"
updatedConfigDirs = "/usr/local/etc/nginx"
Expand Down Expand Up @@ -139,6 +139,9 @@ func TestGetConfig(t *testing.T) {
assert.Equal(t, Defaults.Server.GrpcPort, config.Server.GrpcPort)
assert.Equal(t, Defaults.Server.Command, config.Server.Command)
assert.Equal(t, Defaults.Server.Metrics, config.Server.Metrics)

assert.Equal(t, Defaults.AgentAPI.Port, config.AgentAPI.Port)

assert.True(t, len(config.AllowedDirectoriesMap) > 0)
assert.Equal(t, Defaults.ConfigDirs, config.ConfigDirs)
assert.Equal(t, Defaults.TLS.Enable, config.TLS.Enable)
Expand Down Expand Up @@ -189,11 +192,12 @@ func TestGetConfig(t *testing.T) {
// Check for updated values
assert.Equal(t, updatedLogLevel, config.Log.Level)
assert.Equal(t, updatedTag, config.DisplayName)
assert.Equal(t, []string{updatedTag}, config.Tags)

// Everything else should still be default
assert.Equal(t, Defaults.Server.Host, config.Server.Host)
assert.Equal(t, Defaults.Server.GrpcPort, config.Server.GrpcPort)
assert.Equal(t, []string{updatedTag}, config.Tags)
assert.Equal(t, Defaults.AgentAPI.Port, config.AgentAPI.Port)
})

t.Run("test override defaults with config file values", func(t *testing.T) {
Expand Down Expand Up @@ -249,7 +253,8 @@ func TestGetConfig(t *testing.T) {
require.NoError(t, err)

assert.Equal(t, updatedServerHost, config.Server.Host)
assert.Equal(t, updatedServerPort, config.Server.GrpcPort)
assert.Equal(t, updatedServerGrpcPort, config.Server.GrpcPort)
assert.Equal(t, updatedAgentAPIPort, config.AgentAPI.Port)
assert.Equal(t, updatedConfTags, config.Tags)

// Check for updated values
Expand Down
22 changes: 18 additions & 4 deletions src/core/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"os"
"time"

"github.com/google/uuid"

agent_config "github.com/nginx/agent/sdk/v2/agent/config"

"github.com/google/uuid"
log "github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -45,6 +45,9 @@ var (
// so setting to random uuid at the moment, tls connection won't work without the auth header
Token: uuid.New().String(),
},
AgentAPI: AgentAPI{
Port: 9090,
},
Nginx: Nginx{
Debug: false,
NginxCountingSocket: "unix:/var/run/nginx-agent/nginx.sock",
Expand Down Expand Up @@ -107,11 +110,16 @@ const (
ServerKey = "server"

ServerHost = ServerKey + agent_config.KeyDelimiter + "host"
ServerGrpcport = ServerKey + agent_config.KeyDelimiter + "grpcport"
ServerGrpcPort = ServerKey + agent_config.KeyDelimiter + "grpcport"
ServerToken = ServerKey + agent_config.KeyDelimiter + "token"
ServerMetrics = ServerKey + agent_config.KeyDelimiter + "metrics"
ServerCommand = ServerKey + agent_config.KeyDelimiter + "command"

// viper keys used in config
APIKey = "api"

AgentAPIPort = APIKey + agent_config.KeyDelimiter + "port"

// viper keys used in config
TlsKey = "tls"

Expand Down Expand Up @@ -203,7 +211,7 @@ var (
DefaultValue: Defaults.Server.Host,
},
&IntFlag{
Name: ServerGrpcport,
Name: ServerGrpcPort,
Usage: "The desired GRPC port to use for nginx-agent traffic.",
DefaultValue: Defaults.Server.GrpcPort,
},
Expand All @@ -222,6 +230,12 @@ var (
Usage: "The name of the command server sent in the tls configuration.",
DefaultValue: Defaults.Server.Command,
},
// API Config
&IntFlag{
Name: AgentAPIPort,
Usage: "The desired port to use for nginx-agent to expose for HTTP traffic.",
DefaultValue: Defaults.AgentAPI.Port,
},
&StringFlag{
Name: ConfigDirsKey,
Usage: "Defines the paths that you want to grant nginx-agent read/write access to. This key is formatted as a string and follows Unix PATH format.",
Expand Down
5 changes: 5 additions & 0 deletions src/core/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Config struct {
ClientID string `mapstructure:"agent_id" yaml:"-"`
CloudAccountID string `mapstructure:"cloud_account" yaml:"-"`
Server Server `mapstructure:"server" yaml:"-"`
AgentAPI AgentAPI `mapstructure:"api" yaml:"-"`
ConfigDirs string `mapstructure:"config-dirs" yaml:"-"`
Log LogConfig `mapstructure:"log" yaml:"-"`
TLS TLSConfig `mapstructure:"tls" yaml:"-"`
Expand Down Expand Up @@ -40,6 +41,10 @@ type Server struct {
Target string `mapstructure:"target" yaml:"-"`
}

type AgentAPI struct {
Port int `mapstructure:"port" yaml:"-"`
}

// LogConfig for logging
type LogConfig struct {
Level string `mapstructure:"level" yaml:"-"`
Expand Down
134 changes: 134 additions & 0 deletions src/plugins/agent_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package plugins

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"regexp"

"github.com/nginx/agent/sdk/v2/proto"
"github.com/nginx/agent/v2/src/core"
"github.com/nginx/agent/v2/src/core/config"

log "github.com/sirupsen/logrus"
)

type AgentAPI struct {
config *config.Config
env core.Environment
server http.Server
nginxBinary core.NginxBinary
nginxHandler *NginxHandler
}

type NginxHandler struct {
env core.Environment
nginxBinary core.NginxBinary
}

func NewAgentAPI(config *config.Config, env core.Environment, nginxBinary core.NginxBinary) *AgentAPI {
return &AgentAPI{config: config, env: env, nginxBinary: nginxBinary}
}

func (a *AgentAPI) Init(core.MessagePipeInterface) {
log.Info("Agent API initializing")
go a.createHttpServer()
}

func (a *AgentAPI) Close() {
log.Info("Agent API is wrapping up")
if err := a.server.Shutdown(context.Background()); err != nil {
log.Errorf("Agent API HTTP Server Shutdown Error: %v", err)
}
}

func (a *AgentAPI) Process(message *core.Message) {
log.Tracef("Process function in the agent_api.go, %s %v", message.Topic(), message.Data())
}

func (a *AgentAPI) Info() *core.Info {
return core.NewInfo("Agent API Plugin", "v0.0.1")
}

func (a *AgentAPI) Subscriptions() []string {
return []string{}
}

func (a *AgentAPI) createHttpServer() {
mux := http.NewServeMux()
a.nginxHandler = &NginxHandler{a.env, a.nginxBinary}
mux.Handle("/nginx/", a.nginxHandler)

log.Debug("Starting Agent API HTTP server")

a.server = http.Server{
Addr: fmt.Sprintf(":%d", a.config.AgentAPI.Port),
Handler: mux,
}

if err := a.server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("error listening to port: %v", err)
}
}

var (
instancesRegex = regexp.MustCompile(`^\/nginx[\/]*$`)
)

func (h *NginxHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "application/json")
switch {
case r.Method == http.MethodGet && instancesRegex.MatchString(r.URL.Path):
err := sendInstanceDetailsPayload(h.getNginxDetails(), w, r)
if err != nil {
log.Warnf("Failed to send instance details payload: %v", err)
}
default:
w.WriteHeader(http.StatusNotFound)
_, err := fmt.Fprint(w, []byte("not found"))
if err != nil {
log.Warnf("Failed to send api response: %v", err)
}
}
}

func sendInstanceDetailsPayload(nginxDetails []*proto.NginxDetails, w http.ResponseWriter, r *http.Request) error {
w.WriteHeader(http.StatusOK)

if len(nginxDetails) == 0 {
log.Debug("No nginx instances found")
_, err := fmt.Fprint(w, "[]")
if err != nil {
return fmt.Errorf("failed to send payload: %v", err)
}

return nil
}

respBody := new(bytes.Buffer)
err := json.NewEncoder(respBody).Encode(nginxDetails)
if err != nil {
return fmt.Errorf("failed to encode payload: %v", err)
}

_, err = fmt.Fprint(w, respBody)
if err != nil {
return fmt.Errorf("failed to send payload: %v", err)
}

return nil
}

func (h *NginxHandler) getNginxDetails() []*proto.NginxDetails {
var nginxDetails []*proto.NginxDetails

for _, proc := range h.env.Processes() {
if proc.IsMaster {
nginxDetails = append(nginxDetails, h.nginxBinary.GetNginxDetailsFromProcess(proc))
}
}

return nginxDetails
}
Loading