Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
selecting API with write access
  • Loading branch information
Akshay2191 committed Sep 30, 2025
commit d11d7a5b94b789531213c272b5a442fa360753c8
79 changes: 50 additions & 29 deletions api/grpc/mpi/v1/command.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions api/grpc/mpi/v1/command.pb.validate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions api/grpc/mpi/v1/command.proto
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,8 @@ message NGINXPlusRuntimeInfo {
repeated string dynamic_modules = 5;
// the plus API details
APIDetails plus_api = 6;
// to store all the endpoints details
repeated APIDetails plus_apis = 7;
}

message APIDetails {
Expand All @@ -354,6 +356,8 @@ message APIDetails {
string listen = 2;
// the API CA file path
string Ca = 3;
// flag to know if this API location was configured with 'api write=on;'
bool write_enabled = 4;
}

// A set of runtime NGINX App Protect settings
Expand Down
2 changes: 2 additions & 0 deletions docs/proto/protos.md
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,7 @@ Perform an associated API action on an instance
| location | [string](#string) | | the API location directive |
| listen | [string](#string) | | the API listen directive |
| Ca | [string](#string) | | the API CA file path |
| write_enabled | [bool](#bool) | | flag to know if this API location was configured with 'api write=on;' |



Expand Down Expand Up @@ -1131,6 +1132,7 @@ A set of runtime NGINX Plus settings
| loadable_modules | [string](#string) | repeated | List of NGINX potentially loadable modules (installed but not loaded). |
| dynamic_modules | [string](#string) | repeated | List of NGINX dynamic modules. |
| plus_api | [APIDetails](#mpi-v1-APIDetails) | | the plus API details |
| plus_apis | [APIDetails](#mpi-v1-APIDetails) | repeated | to store all the endpoints details |



Expand Down
9 changes: 5 additions & 4 deletions internal/model/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ type NginxConfigContext struct {
}

type APIDetails struct {
URL string
Listen string
Location string
Ca string
URL string
Listen string
Location string
Ca string
WriteEnabled bool
}

type ManifestFile struct {
Expand Down
37 changes: 29 additions & 8 deletions internal/resource/resource_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,21 +361,42 @@ func convertToStreamUpstreamServer(streamUpstreams []*structpb.Struct) []client.
}

func (r *ResourceService) createPlusClient(ctx context.Context, instance *mpi.Instance) (*client.NginxClient, error) {
plusAPI := instance.GetInstanceRuntime().GetNginxPlusRuntimeInfo().GetPlusApi()
plusAPIs := instance.GetInstanceRuntime().GetNginxPlusRuntimeInfo().GetPlusApis()
var endpoint string
var selectedAPI *mpi.APIDetails

if plusAPI.GetLocation() == "" || plusAPI.GetListen() == "" {
if len(plusAPIs) == 0 {
return nil, errors.New("failed to preform API action, NGINX Plus API is not configured")
}

if strings.HasPrefix(plusAPI.GetListen(), "unix:") {
endpoint = fmt.Sprintf(unixPlusAPIFormat, plusAPI.GetLocation())
for _, api := range plusAPIs {
if api.GetWriteEnabled() {
selectedAPI = api
slog.DebugContext(ctx, "Selected write-enabled NGINX Plus API for action",
"url", selectedAPI.GetLocation(), "listen", selectedAPI.GetListen())

break
}
}

if selectedAPI == nil {
selectedAPI = plusAPIs[0]
slog.InfoContext(ctx, "No write-enabled NGINX Plus API found. Write operations may fail.",
"url", selectedAPI.GetLocation(), "listen", selectedAPI.GetListen())
}

if selectedAPI.GetLocation() == "" || selectedAPI.GetListen() == "" {
return nil, errors.New("failed to preform API action, NGINX Plus API is not configured")
}

if strings.HasPrefix(selectedAPI.GetListen(), "unix:") {
endpoint = fmt.Sprintf(unixPlusAPIFormat, selectedAPI.GetLocation())
} else {
endpoint = fmt.Sprintf(apiFormat, plusAPI.GetListen(), plusAPI.GetLocation())
endpoint = fmt.Sprintf(apiFormat, selectedAPI.GetListen(), selectedAPI.GetLocation())
}

httpClient := http.DefaultClient
caCertLocation := plusAPI.GetCa()
caCertLocation := selectedAPI.GetCa()
if caCertLocation != "" {
slog.DebugContext(ctx, "Reading CA certificate", "file_path", caCertLocation)
caCert, err := os.ReadFile(caCertLocation)
Expand All @@ -394,8 +415,8 @@ func (r *ResourceService) createPlusClient(ctx context.Context, instance *mpi.In
},
}
}
if strings.HasPrefix(plusAPI.GetListen(), "unix:") {
httpClient = socketClient(ctx, strings.TrimPrefix(plusAPI.GetListen(), "unix:"))
if strings.HasPrefix(selectedAPI.GetListen(), "unix:") {
httpClient = socketClient(ctx, strings.TrimPrefix(selectedAPI.GetListen(), "unix:"))
}

return client.NewNginxClient(endpoint,
Expand Down
35 changes: 20 additions & 15 deletions internal/resource/resource_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,24 +246,29 @@ func TestResourceService_createPlusClient(t *testing.T) {
err := os.WriteFile(caFile, []byte("-----BEGIN CERTIFICATE-----\nMII...\n-----END CERTIFICATE-----"), 0o600)
require.NoError(t, err)

instanceWithAPI := protos.NginxPlusInstance([]string{})
instanceWithAPI.InstanceRuntime.GetNginxPlusRuntimeInfo().PlusApi = &v1.APIDetails{
Location: "/api",
Listen: "localhost:80",
}
createPlusInstanceWithApis := func(details []*v1.APIDetails) *v1.Instance {
inst := protos.NginxPlusInstance([]string{})
if inst.GetInstanceRuntime().GetNginxPlusRuntimeInfo() == nil {
inst.InstanceRuntime.Details = &v1.InstanceRuntime_NginxPlusRuntimeInfo{
NginxPlusRuntimeInfo: &v1.NGINXPlusRuntimeInfo{},
}
}
inst.InstanceRuntime.GetNginxPlusRuntimeInfo().PlusApis = details

instanceWithUnixAPI := protos.NginxPlusInstance([]string{})
instanceWithUnixAPI.InstanceRuntime.GetNginxPlusRuntimeInfo().PlusApi = &v1.APIDetails{
Listen: "unix:/var/run/nginx-status.sock",
Location: "/api",
return inst
}

instanceWithCACert := protos.NginxPlusInstance([]string{})
instanceWithCACert.InstanceRuntime.GetNginxPlusRuntimeInfo().PlusApi = &v1.APIDetails{
Location: "/api",
Listen: "localhost:443",
Ca: caFile,
}
instanceWithAPI := createPlusInstanceWithApis([]*v1.APIDetails{
{Location: "/api", Listen: "localhost:80"},
})

instanceWithUnixAPI := createPlusInstanceWithApis([]*v1.APIDetails{
{Listen: "unix:/var/run/nginx-status.sock", Location: "/api"},
})

instanceWithCACert := createPlusInstanceWithApis([]*v1.APIDetails{
{Location: "/api", Listen: "localhost:443", Ca: caFile},
})

ctx := context.Background()
tests := []struct {
Expand Down