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
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![License](https://img.shields.io/github/license/opsorch/opsorch-core)](https://github.com/opsorch/opsorch-core/blob/main/LICENSE)
[![CI](https://github.com/opsorch/opsorch-core/workflows/CI/badge.svg)](https://github.com/opsorch/opsorch-core/actions)

OpsOrch Core is a stateless, open-source orchestration layer that unifies incident, log, metric, ticket, and messaging workflows behind a single, provider-agnostic API.
OpsOrch Core is a stateless, open-source orchestration layer that unifies incident, log, metric, ticket, messaging, and deployment workflows behind a single, provider-agnostic API.
It does not store operational data, and it does not include any built-in vendor integrations.
External adapters implement provider logic and are loaded dynamically by OpsOrch Core.

Expand All @@ -27,7 +27,7 @@ Adapters live in separate repos such as:

OpsOrch Core never links vendor logic directly. Each capability is resolved at runtime by either importing an **in-process provider** (Go package that registers itself) or by launching a **local plugin binary** that speaks OpsOrch's stdio RPC protocol. At startup OpsOrch checks for environment overrides first, then falls back to any persisted configuration stored via the secret provider.

Environment variables for any capability (`incident`, `alert`, `log`, `metric`, `ticket`, `messaging`, `service`, `secret`):
Environment variables for any capability (`incident`, `alert`, `log`, `metric`, `ticket`, `messaging`, `service`, `deployment`, `secret`):
- `OPSORCH_<CAP>_PROVIDER=<registered name>` – name passed to the corresponding registry
- `OPSORCH_<CAP>_CONFIG=<json>` – decrypted config map forwarded to the constructor
- `OPSORCH_<CAP>_PLUGIN=/path/to/binary` – optional local plugin that overrides `OPSORCH_<CAP>_PROVIDER`
Expand Down Expand Up @@ -103,6 +103,21 @@ curl -s -X POST http://localhost:8080/metrics/query \

# Discover Metrics (requires metric provider)
curl -s "http://localhost:8080/metrics/describe?service=api"

# Query Deployments (requires deployment provider)
curl -s -X POST http://localhost:8080/deployments/query \
-H "Content-Type: application/json" \
-d '{
"statuses": ["success", "failed"],
"scope": {
"service": "api-service",
"environment": "production"
},
"limit": 10
}'

# Get a specific deployment (requires deployment provider)
curl -s http://localhost:8080/deployments/deploy-123
```

Add `-H "Authorization: Bearer <token>"` to each curl when `OPSORCH_BEARER_TOKEN` is set.
Expand Down Expand Up @@ -203,6 +218,7 @@ OpsOrch exposes API endpoints for:
- Tickets
- Messaging
- Services
- Deployments

Schemas live under `schema/` and evolve as the system matures.

Expand Down
2 changes: 2 additions & 0 deletions api/capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func normalizeCapability(name string) (string, bool) {
return "messaging", true
case "service", "services":
return "service", true
case "deployment", "deployments":
return "deployment", true
default:
return "", false
}
Expand Down
83 changes: 83 additions & 0 deletions api/deployment_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package api

import (
"fmt"
"net/http"
"strings"

"github.com/opsorch/opsorch-core/deployment"
"github.com/opsorch/opsorch-core/orcherr"
"github.com/opsorch/opsorch-core/schema"
)

// DeploymentHandler wraps provider wiring for deployments.
type DeploymentHandler struct {
provider deployment.Provider
}

func newDeploymentHandlerFromEnv(sec SecretProvider) (DeploymentHandler, error) {
name, cfg, pluginPath, err := loadProviderConfig(sec, "deployment", "OPSORCH_DEPLOYMENT_PROVIDER", "OPSORCH_DEPLOYMENT_CONFIG", "OPSORCH_DEPLOYMENT_PLUGIN")
if err != nil || (name == "" && pluginPath == "") {
return DeploymentHandler{}, err
}
if pluginPath != "" {
return DeploymentHandler{provider: newDeploymentPluginProvider(pluginPath, cfg)}, nil
}
constructor, ok := deployment.LookupProvider(name)
if !ok {
return DeploymentHandler{}, fmt.Errorf("deployment provider %s not registered", name)
}
provider, err := constructor(cfg)
if err != nil {
return DeploymentHandler{}, err
}
return DeploymentHandler{provider: provider}, nil
}

// handleDeployment handles deployment HTTP requests from the server
func (s *Server) handleDeployment(w http.ResponseWriter, r *http.Request) bool {
return s.deployment.handleDeploymentRequest(w, r)
}

// handleDeploymentRequest handles deployment HTTP requests
func (h *DeploymentHandler) handleDeploymentRequest(w http.ResponseWriter, r *http.Request) bool {
if !strings.HasPrefix(r.URL.Path, "/deployments") {
return false
}
if h.provider == nil {
writeError(w, http.StatusNotImplemented, orcherr.OpsOrchError{Code: "deployment_provider_missing", Message: "deployment provider not configured"})
return true
}

path := strings.TrimSuffix(r.URL.Path, "/")
segments := strings.Split(strings.Trim(path, "/"), "/")

switch {
case len(segments) == 2 && segments[1] == "query" && r.Method == http.MethodPost:
var query schema.DeploymentQuery
if err := decodeJSON(r, &query); err != nil {
writeError(w, http.StatusBadRequest, orcherr.OpsOrchError{Code: "bad_request", Message: err.Error()})
return true
}
deployments, err := h.provider.Query(r.Context(), query)
if err != nil {
writeProviderError(w, err)
return true
}
logAudit(r, "deployment.query")
writeJSON(w, http.StatusOK, deployments)
return true
case len(segments) == 2 && r.Method == http.MethodGet:
id := segments[1]
deployment, err := h.provider.Get(r.Context(), id)
if err != nil {
writeProviderError(w, err)
return true
}
logAudit(r, "deployment.get")
writeJSON(w, http.StatusOK, deployment)
return true
default:
return false
}
}
Loading
Loading