mirror of
https://github.com/open-gitagent/langship.sh.git
synced 2026-08-03 07:21:04 +02:00
feat: implement environment management features
- Added environment creation and editing pages with forms for name and description. - Integrated environment listing with options to edit and delete environments. - Updated agent detail page to manage environments followed by agents. - Enhanced API to support environment operations including listing, creating, updating, and deleting environments. - Refactored related components and state management for improved clarity and functionality.
This commit is contained in:
@@ -72,6 +72,12 @@ func (m *Mongo) Credentials() CredentialStore {
|
||||
return &mongoCredentials{coll: m.db.Collection("credentials")}
|
||||
}
|
||||
|
||||
// Environments returns the global EnvironmentStore backed by this Mongo
|
||||
// connection.
|
||||
func (m *Mongo) Environments() EnvironmentStore {
|
||||
return &mongoEnvironments{coll: m.db.Collection("environments")}
|
||||
}
|
||||
|
||||
func (m *Mongo) ensureIndexes(ctx context.Context) error {
|
||||
if _, err := m.db.Collection("pipelines").Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||
{Keys: bson.D{{Key: "updated_at", Value: -1}}},
|
||||
@@ -95,6 +101,12 @@ func (m *Mongo) ensureIndexes(ctx context.Context) error {
|
||||
}); err != nil {
|
||||
return fmt.Errorf("credentials indexes: %w", err)
|
||||
}
|
||||
if _, err := m.db.Collection("environments").Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||
{Keys: bson.D{{Key: "name", Value: 1}}, Options: options.Index().SetUnique(true)},
|
||||
{Keys: bson.D{{Key: "updated_at", Value: -1}}},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("environments indexes: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -522,3 +534,63 @@ func (s *mongoCredentials) List(ctx context.Context) ([]*Credential, error) {
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// --- environments (global) -----------------------------------------------
|
||||
|
||||
type mongoEnvironments struct{ coll *mongo.Collection }
|
||||
|
||||
func (s *mongoEnvironments) Create(ctx context.Context, e *Environment) error {
|
||||
if _, err := s.coll.InsertOne(ctx, e); err != nil {
|
||||
if mongo.IsDuplicateKeyError(err) {
|
||||
return ErrAlreadyExists
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *mongoEnvironments) GetByName(ctx context.Context, name string) (*Environment, error) {
|
||||
var e Environment
|
||||
if err := s.coll.FindOne(ctx, bson.M{"name": name}).Decode(&e); err != nil {
|
||||
if errors.Is(err, mongo.ErrNoDocuments) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (s *mongoEnvironments) Update(ctx context.Context, e *Environment) error {
|
||||
res, err := s.coll.ReplaceOne(ctx, bson.M{"name": e.Name}, e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.MatchedCount == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *mongoEnvironments) Delete(ctx context.Context, name string) error {
|
||||
res, err := s.coll.DeleteOne(ctx, bson.M{"name": name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.DeletedCount == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *mongoEnvironments) List(ctx context.Context) ([]*Environment, error) {
|
||||
cur, err := s.coll.Find(ctx, bson.M{}, options.Find().SetSort(bson.D{{Key: "name", Value: 1}}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cur.Close(ctx)
|
||||
var out []*Environment
|
||||
if err := cur.All(ctx, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
+54
-1
@@ -132,9 +132,14 @@ type Agent struct {
|
||||
WebhookInstalledAt *time.Time `json:"webhookInstalledAt,omitempty" bson:"webhook_installed_at,omitempty"`
|
||||
AuthStatus AuthStatus `json:"authStatus,omitempty" bson:"auth_status,omitempty"`
|
||||
AuthCheckedAt *time.Time `json:"authCheckedAt,omitempty" bson:"auth_checked_at,omitempty"`
|
||||
AttachedPipelines []string `json:"attachedPipelines,omitempty" bson:"attached_pipelines,omitempty"`
|
||||
// Environments this agent follows by name. Triggering the agent runs
|
||||
// the pipelines of these environments (filtered by branch). Replaces
|
||||
// the older flat AttachedPipelines list — pipelines now live on the
|
||||
// environment, and agents subscribe to environments.
|
||||
Environments []string `json:"environments,omitempty" bson:"environments,omitempty"`
|
||||
|
||||
// Named credentials — referenced by name from Deploy / future nodes.
|
||||
// These are agent-specific overrides of the global credential pool.
|
||||
Credentials []Credential `json:"credentials,omitempty" bson:"credentials,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt" bson:"created_at"`
|
||||
@@ -177,3 +182,51 @@ type CredentialStore interface {
|
||||
Delete(ctx context.Context, name string) error
|
||||
List(ctx context.Context) ([]*Credential, error)
|
||||
}
|
||||
|
||||
// Environment is a global, named deploy stage (dev / staging / prod /
|
||||
// custom — the name is free-form). It is purely a sequencing container:
|
||||
// it owns an ordered list of pipeline IDs (the promotion sequence) plus
|
||||
// a description. Per-deploy config (credential, runtime target, approval
|
||||
// method) lives on the nodes themselves, not here.
|
||||
//
|
||||
// Agents subscribe to environments by name (agent.Environments);
|
||||
// triggering an agent runs the pipelines of the environments it follows
|
||||
// (each pipeline still gated by its Trigger node's branch filter).
|
||||
//
|
||||
// A pipeline may appear in more than one environment.
|
||||
type Environment struct {
|
||||
ID string `json:"id" bson:"_id"`
|
||||
Name string `json:"name" bson:"name"` // unique
|
||||
Description string `json:"description,omitempty" bson:"description,omitempty"`
|
||||
|
||||
// PipelineIDs is ordered — the order is the promotion sequence and is
|
||||
// reorderable via the API. Dispatch still applies each pipeline's own
|
||||
// branch filter; the order is the documented progression.
|
||||
PipelineIDs []string `json:"pipelineIds,omitempty" bson:"pipeline_ids,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt" bson:"created_at"`
|
||||
UpdatedAt time.Time `json:"updatedAt" bson:"updated_at"`
|
||||
}
|
||||
|
||||
// HasPipeline reports whether the pipeline ID is brought into this env.
|
||||
func (e *Environment) HasPipeline(pipelineID string) bool {
|
||||
if e == nil {
|
||||
return false
|
||||
}
|
||||
for _, p := range e.PipelineIDs {
|
||||
if p == pipelineID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// EnvironmentStore persists global environments. Lookup is by name (the
|
||||
// user-facing identifier — pipelines/agents reference envs by name).
|
||||
type EnvironmentStore interface {
|
||||
Create(ctx context.Context, e *Environment) error
|
||||
GetByName(ctx context.Context, name string) (*Environment, error)
|
||||
Update(ctx context.Context, e *Environment) error
|
||||
Delete(ctx context.Context, name string) error
|
||||
List(ctx context.Context) ([]*Environment, error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user