Skip to content

Commit

Permalink
enhance: create API endpoints for setting environment variables
Browse files Browse the repository at this point in the history
These endpoints are added for workflows and agents. Setting the
environment variables for a workflow or agent will ensure that the
object's manifest has the same information.

Signed-off-by: Donnie Adams <[email protected]>
  • Loading branch information
thedadams committed Dec 18, 2024
1 parent b8f7a86 commit bf5b56b
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 4 deletions.
132 changes: 132 additions & 0 deletions pkg/api/handlers/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package handlers

import (
"context"
"errors"
"fmt"
"slices"
"strings"

"github.com/gptscript-ai/go-gptscript"
"github.com/obot-platform/obot/apiclient/types"
"github.com/obot-platform/obot/pkg/api"
v1 "github.com/obot-platform/obot/pkg/storage/apis/otto.otto8.ai/v1"
"github.com/obot-platform/obot/pkg/system"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
)

func SetEnv(req api.Context) error {
id := req.PathValue("id")
if id == "" {
return types.NewErrBadRequest("id path variable is required")
}

var envs map[string]string
if err := req.Read(&envs); err != nil {
return err
}

var errs []error
for key, val := range envs {
if err := req.GPTClient.DeleteCredential(req.Context(), id, key); err != nil && !strings.HasSuffix(err.Error(), "credential not found") {
errs = append(errs, fmt.Errorf("failed to remove existing credetial %q: %w", key, err))
continue
}

if err := req.GPTClient.CreateCredential(req.Context(), gptscript.Credential{
Context: id,
ToolName: key,
Type: gptscript.CredentialTypeTool,
Env: map[string]string{key: val},
}); err != nil {
errs = append(errs, fmt.Errorf("failed to create credential %q: %w", key, err))
}
}

if len(errs) > 0 {
return errors.Join(errs...)
}

obj, env, err := getObjectAndEnv(req.Context(), req.Storage, req.Namespace(), id)
if err != nil {
return err
}

for i := 0; i < len(*env); i++ {
if _, ok := envs[(*env)[i].Name]; !ok {
// Delete the credential for the store
if err := req.GPTClient.DeleteCredential(req.Context(), id, (*env)[i].Name); err != nil && !strings.HasSuffix(err.Error(), "credential not found") {
errs = append(errs, fmt.Errorf("failed to remove existing credetial %q that is not longer needed: %w", (*env)[i].Name, err))
continue
}
// Remove the item from the slice
*env = append((*env)[:i], (*env)[i+1:]...)
i--
}
}

if len(errs) > 0 {
return errors.Join(errs...)
}

for name := range envs {
if !slices.ContainsFunc(*env, func(envVar types.EnvVar) bool {
return envVar.Name == name
}) {
*env = append(*env, types.EnvVar{Name: name})
}
}

if err = req.Update(obj); err != nil {
return fmt.Errorf("failed to update %s: %w", obj.GetObjectKind().GroupVersionKind().Kind, err)
}

return nil
}

func RevealEnv(req api.Context) error {
id := req.PathValue("id")
if id == "" {
return types.NewErrBadRequest("id path variable is required")
}

_, env, err := getObjectAndEnv(req.Context(), req.Storage, req.Namespace(), id)
if err != nil {
return err
}

resp := make(map[string]string, len(*env))
for _, e := range *env {
cred, err := req.GPTClient.RevealCredential(req.Context(), []string{id}, e.Name)
if err != nil && !strings.HasSuffix(err.Error(), "credential not found") {
return err
}

resp[e.Name] = cred.Env[e.Name]
}

return req.Write(resp)
}

func getObjectAndEnv(ctx context.Context, client kclient.Client, namespace, id string) (kclient.Object, *[]types.EnvVar, error) {
switch {
case system.IsAgentID(id):
var agent v1.Agent
if err := client.Get(ctx, kclient.ObjectKey{Namespace: namespace, Name: id}, &agent); err != nil {
return nil, nil, err
}

return &agent, &agent.Spec.Manifest.Env, nil

case system.IsWorkflowID(id):
var wf v1.Workflow
if err := client.Get(ctx, kclient.ObjectKey{Namespace: namespace, Name: id}, &wf); err != nil {
return nil, nil, err
}

return &wf, &wf.Spec.Manifest.Env, nil

default:
return nil, nil, types.NewErrBadRequest("%s is not an agent nor workflow", id)
}
}
3 changes: 0 additions & 3 deletions pkg/api/handlers/workflows.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ func (a *WorkflowHandler) Authenticate(req api.Context) error {
}

agent.Spec.Manifest.Prompt = "#!sys.echo\nDONE"
if len(agent.Spec.Credentials) == 0 {
return nil
}

resp, err := a.invoker.Agent(req.Context(), req.Storage, agent, "", invoke.Options{
Synchronous: true,
Expand Down
6 changes: 6 additions & 0 deletions pkg/api/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ func Router(services *services.Services) (http.Handler, error) {
mux.HandleFunc("DELETE /api/workflows/{context}/credentials/{id}", handlers.DeleteCredential)
mux.HandleFunc("DELETE /api/credentials/{id}", handlers.DeleteCredential)

// Environment variable credentials
mux.HandleFunc("POST /api/workflows/{id}/env", handlers.SetEnv)
mux.HandleFunc("GET /api/workflows/{id}/env", handlers.RevealEnv)
mux.HandleFunc("POST /api/agents/{id}/env", handlers.SetEnv)
mux.HandleFunc("GET /api/agents/{id}/env", handlers.RevealEnv)

// Webhooks
mux.HandleFunc("POST /api/webhooks", webhooks.Create)
mux.HandleFunc("GET /api/webhooks", webhooks.List)
Expand Down
2 changes: 1 addition & 1 deletion pkg/gateway/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (s *Server) AddRoutes(mux *server.Server) {
mux.HandleFunc("GET /api/oauth/start/{id}/{service}", wrap(s.oauth))
mux.HandleFunc("/api/oauth/redirect/{service}", wrap(s.redirect))

// CRUD routes for OAuth Apps (integrations with other service such as Microsoft 365)
// CRUD routes for OAuth Apps (integrations with other services such as Microsoft 365)
mux.HandleFunc("GET /api/oauth-apps", wrap(s.listOAuthApps))
mux.HandleFunc("GET /api/oauth-apps/{id}", wrap(s.oauthAppByID))
mux.HandleFunc("POST /api/oauth-apps", wrap(s.createOAuthApp))
Expand Down

0 comments on commit bf5b56b

Please sign in to comment.