Skip to content

Commit

Permalink
Merge pull request #22 from get-glu/gm/rename-controllers-phases
Browse files Browse the repository at this point in the history
refactor: rename controller to phase
  • Loading branch information
GeorgeMac authored Nov 13, 2024
2 parents c28a189 + 04e2110 commit ac88c5a
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 226 deletions.
44 changes: 22 additions & 22 deletions cmd/experiment/experiment.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"time"

"github.com/get-glu/glu"
"github.com/get-glu/glu/pkg/controllers"
"github.com/get-glu/glu/pkg/core"
"github.com/get-glu/glu/pkg/fs"
"github.com/get-glu/glu/pkg/phases"
"github.com/get-glu/glu/pkg/src/git"
"github.com/get-glu/glu/pkg/src/oci"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -39,23 +39,23 @@ func run(ctx context.Context) error {
// create initial (empty) pipeline
pipeline := glu.NewPipeline(glu.Name("checkout"), NewCheckoutResource)

// build a controller which sources from the OCI repository
ociController, err := controllers.New(glu.Name("oci"), pipeline, ociSource)
// build a phase which sources from the OCI repository
ociPhase, err := phases.New(glu.Name("oci"), pipeline, ociSource)
if err != nil {
return nil, err
}

// build a controller for the staging environment which source from the git repository
// configure it to promote from the OCI controller
staging, err := controllers.New(glu.Name("staging", glu.Label("env", "staging")),
pipeline, gitSource, core.PromotesFrom(ociController))
// build a phase for the staging environment which source from the git repository
// configure it to promote from the OCI phase
staging, err := phases.New(glu.Name("staging", glu.Label("env", "staging")),
pipeline, gitSource, core.PromotesFrom(ociPhase))
if err != nil {
return nil, err
}

// build a controller for the production environment which source from the git repository
// configure it to promote from the staging git controller
_, err = controllers.New(glu.Name("production", glu.Label("env", "production")),
// build a phase for the production environment which source from the git repository
// configure it to promote from the staging git phase
_, err = phases.New(glu.Name("production", glu.Label("env", "production")),
pipeline, gitSource, core.PromotesFrom(staging))
if err != nil {
return nil, err
Expand All @@ -66,8 +66,8 @@ func run(ctx context.Context) error {
}).SchedulePromotion(
glu.ScheduleInterval(10*time.Second),
glu.ScheduleMatchesLabel("env", "staging"),
// alternatively, the controller instance can be target directly with:
// glu.ScheduleMatchesController(gitStaging),
// alternatively, the phase instance can be target directly with:
// glu.ScheduleMatchesPhase(gitStaging),
).Run()
}

Expand All @@ -87,26 +87,26 @@ func NewCheckoutResource() *CheckoutResource {
// It should return a unique digest for the state of the resource.
// In this instance we happen to be reading a unique digest from the source
// and so we can lean into that.
// This will be used for comparisons in the controller to decided whether or not
// This will be used for comparisons in the phase to decided whether or not
// a change has occurred when deciding if to update the target source.
func (c *CheckoutResource) Digest() (string, error) {
return c.ImageDigest, nil
}

// CommitMessage is an optional git specific method for overriding generated commit messages.
// The function is provided with the source controllers metadata and the previous value of resource.
// The function is provided with the source phases metadata and the previous value of resource.
func (c *CheckoutResource) CommitMessage(meta glu.Metadata, _ *CheckoutResource) (string, error) {
return fmt.Sprintf("feat: update app %q in %q", meta.Name, meta.Labels["env"]), nil
}

// ProposalTitle is an optional git specific method for overriding generated proposal message (PR/MR) title message.
// The function is provided with the source controllers metadata and the previous value of resource.
// The function is provided with the source phases metadata and the previous value of resource.
func (c *CheckoutResource) ProposalTitle(meta glu.Metadata, r *CheckoutResource) (string, error) {
return c.CommitMessage(meta, r)
}

// ProposalBody is an optional git specific method for overriding generated proposal body (PR/MR) body message.
// The function is provided with the source controllers metadata and the previous value of resource.
// The function is provided with the source phases metadata and the previous value of resource.
func (c *CheckoutResource) ProposalBody(meta glu.Metadata, r *CheckoutResource) (string, error) {
return fmt.Sprintf(`| app | from | to |
| -------- | ---- | -- |
Expand All @@ -125,9 +125,9 @@ func (c *CheckoutResource) ReadFromOCIDescriptor(d v1.Descriptor) error {
// ReadFrom is a Git specific resource requirement.
// It specifies how to read the resource from a target Filesystem.
// The type should navigate and source the relevant state from the fileystem provided.
// The function is also provided with metadata for the calling controller.
// This allows the defining type to adjust behaviour based on the context of the controller.
// Here we are reading a yaml file from a directory signified by a label ("env") on the controller metadata.
// The function is also provided with metadata for the calling phase.
// This allows the defining type to adjust behaviour based on the context of the phase.
// Here we are reading a yaml file from a directory signified by a label ("env") on the phase metadata.
func (c *CheckoutResource) ReadFrom(_ context.Context, meta core.Metadata, fs fs.Filesystem) error {
fi, err := fs.OpenFile(
fmt.Sprintf("/env/%s/apps/checkout/deployment.yaml", meta.Labels["env"]),
Expand Down Expand Up @@ -155,9 +155,9 @@ func (c *CheckoutResource) ReadFrom(_ context.Context, meta core.Metadata, fs fs
// WriteTo is a Git specific resource requirement.
// It specifies how to write the resource to a target Filesystem.
// The type should navigate and encode the state of the resource to the target Filesystem.
// The function is also provided with metadata for the calling controller.
// This allows the defining type to adjust behaviour based on the context of the controller.
// Here we are writing a yaml file to a directory signified by a label ("env") on the controller metadata.
// The function is also provided with metadata for the calling phase.
// This allows the defining type to adjust behaviour based on the context of the phase.
// Here we are writing a yaml file to a directory signified by a label ("env") on the phase metadata.
func (c *CheckoutResource) WriteTo(ctx context.Context, meta glu.Metadata, fs fs.Filesystem) error {
fi, err := fs.OpenFile(
fmt.Sprintf("/env/%s/apps/checkout/deployment.yaml", meta.Labels["env"]),
Expand Down
26 changes: 13 additions & 13 deletions cmd/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"os"

"github.com/get-glu/glu"
"github.com/get-glu/glu/pkg/controllers"
"github.com/get-glu/glu/pkg/core"
"github.com/get-glu/glu/pkg/phases"
)

// MockResource represents our mock resource state
Expand Down Expand Up @@ -42,7 +42,7 @@ func run(ctx context.Context) error {

// OCI phase
ociSource := NewMockSource()
ociController, err := controllers.New(
ociPhase, err := phases.New(
glu.Name("cloud-controller-oci", glu.Label("type", "oci"), glu.Label("status", "running"), glu.Label("version", "v1.2.3")),
ccPipeline,
ociSource,
Expand All @@ -53,23 +53,23 @@ func run(ctx context.Context) error {

// Staging phase
stagingSource := NewMockSource()
stagingController, err := controllers.New(
stagingPhase, err := phases.New(
glu.Name("cloud-controller-staging",
glu.Label("type", "staging"),
glu.Label("environment", "staging"),
glu.Label("region", "us-east-1"),
),
ccPipeline,
stagingSource,
core.PromotesFrom(ociController),
core.PromotesFrom(ociPhase),
)
if err != nil {
return nil, err
}

// Production phases
prodEastSource := NewMockSource()
controllers.New(
phases.New(
glu.Name("cloud-controller-production-east-1",
glu.Label("type", "production"),
glu.Label("environment", "production"),
Expand All @@ -78,11 +78,11 @@ func run(ctx context.Context) error {
),
ccPipeline,
prodEastSource,
core.PromotesFrom(stagingController),
core.PromotesFrom(stagingPhase),
)

prodWestSource := NewMockSource()
controllers.New(
phases.New(
glu.Name("cloud-controller-production-west-1",
glu.Label("type", "production"),
glu.Label("environment", "production"),
Expand All @@ -91,7 +91,7 @@ func run(ctx context.Context) error {
),
ccPipeline,
prodWestSource,
core.PromotesFrom(stagingController),
core.PromotesFrom(stagingPhase),
)

return ccPipeline, nil
Expand All @@ -103,7 +103,7 @@ func run(ctx context.Context) error {

// OCI phase
fdOciSource := NewMockSource()
fdOciController, err := controllers.New(
fdOciPhase, err := phases.New(
glu.Name("frontdoor-oci", glu.Label("type", "oci"), glu.Label("version", "v2.0.1"), glu.Label("builder", "docker")),
fdPipeline,
fdOciSource,
Expand All @@ -114,23 +114,23 @@ func run(ctx context.Context) error {

// Staging phase
fdStagingSource := NewMockSource()
fdStagingController, err := controllers.New(
fdStagingPhase, err := phases.New(
glu.Name("frontdoor-staging",
glu.Label("type", "staging"),
glu.Label("environment", "staging"),
glu.Label("domain", "stage.example.com"),
),
fdPipeline,
fdStagingSource,
core.PromotesFrom(fdOciController),
core.PromotesFrom(fdOciPhase),
)
if err != nil {
return nil, err
}

// Production phase
fdProdSource := NewMockSource()
controllers.New(
phases.New(
glu.Name("frontdoor-production",
glu.Label("type", "production"),
glu.Label("environment", "production"),
Expand All @@ -139,7 +139,7 @@ func run(ctx context.Context) error {
),
fdPipeline,
fdProdSource,
core.PromotesFrom(fdStagingController),
core.PromotesFrom(fdStagingPhase),
)

return fdPipeline, nil
Expand Down
56 changes: 28 additions & 28 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,30 @@ They're also not immutable and we would love for your help in shaping them.
Glu has an opinionated set of models and abstractions, which when combined, allow you to build consistent command-line and server processes for orchestrating the progression of applications and configuration across target environments.

```go
// build a controller which sources from our OCI repository
ociController, err := controllers.New(glu.Name("oci"), pipeline, ociSource)
// build a phase which sources from our OCI repository
ociPhase, err := phases.New(glu.Name("oci"), pipeline, ociSource)
if err != nil {
return nil, err
}

// build a controller for the staging environment which sources from our git repository
// and configures it to promote from the OCI controller
staging, err := controllers.New(glu.Name("staging", glu.Label("env", "staging")),
pipeline, gitSource, core.PromotesFrom(ociController))
// build a phase for the staging environment which sources from our git repository
// and configures it to promote from the OCI phase
staging, err := phases.New(glu.Name("staging", glu.Label("env", "staging")),
pipeline, gitSource, core.PromotesFrom(ociPhase))
if err != nil {
return nil, err
}

// build a controller for the production environment which also sources from our git repository
// and configures it to promote from the staging git controller
_, err = controllers.New(glu.Name("production", glu.Label("env", "production")),
// build a phase for the production environment which also sources from our git repository
// and configures it to promote from the staging git phase
_, err = phases.New(glu.Name("production", glu.Label("env", "production")),
pipeline, gitSource, core.PromotesFrom(staging))
if err != nil {
return nil, err
}
```

The Glu framework comprises of a set of abstractions for declaring the resources (your applications and configuration), update strategies (we call them controllers) and rules for progression (how and when to promote) within a pipeline.
The Glu framework comprises of a set of abstractions for declaring the resources (your applications and configuration), update strategies (we call them phases) and rules for progression (how and when to promote) within a pipeline.

### System

Expand Down Expand Up @@ -74,7 +74,7 @@ Resources are the primary definition of _what_ is being represented in your pipe
In Go, resources are represented as an interface for the author (that's you) to fill in the blanks:

```go
// Resource is an instance of a resource in a controller.
// Resource is an instance of a resource in a phase.
// Primarilly, it exposes a Digest method used to produce
// a hash digest of the instances current state.
type Resource interface {
Expand All @@ -100,39 +100,39 @@ func (s *SomeResource) Digest() (string, error) { return s.ImageDigest, nil }

### Pipelines

Pipelines carry your specific resources type across controller destinations.
Pipelines carry your specific resources type across phase destinations.
They coordinate the edges in the workflow that is your promotion pipeline.

They are also responsible for constructing new instances of your resource types.
These are used when fetching and updating controllers and sources during promotion.
These are used when fetching and updating phases and sources during promotion.

```go
pipeline := glu.NewPipeline(glu.Name("mypipeline"), func() *SomeResource {
// this is an opportunity to set any initial default values
// for fields before the rest of the fields are populated
// from a target controller source
// from a target phase source
return &SomeResource{ImageName: "someimagename"}
})
```

### Controllers
### Phases

Controllers have the job of interfacing with both sources and other upstream controllers to manage the promotion lifecycle.
Phases have the job of interfacing with both sources and other upstream phases to manage the promotion lifecycle.
They have metadata to uniquely identify themselves within the context of a pipeline.
They're also bound to a particular source implementation, and are optional dependent on an upstream source of the _same resource type_.

When a controller attempts a promotion (`controller.Promote(ctx)`) the following occurs:
When a phase attempts a promotion (`phase.Promote(ctx)`) the following occurs:

1. If there is no upstream controller to promote from, then return (no op).
2. Get the current resource state from the target source based on controller metadata.
3. Get the current resource state from the upstream promotion target controller.
1. If there is no upstream phase to promote from, then return (no op).
2. Get the current resource state from the target source based on phase metadata.
3. Get the current resource state from the upstream promotion target phase.
4. If the resource from (2) is equal to that of (3) (based on comparing their digest), the return (no op).
5. Update the state of the source with the state of the upstream resource (3) (this is a promotion).

### Sources

Sources are the core engine for controllers to both view and update resources in a target external system.
While controllers handle the lifecycle of promotion, sources interface with your resource types and sources of truth to perform relevant transactions.
Sources are the core engine for phases to both view and update resources in a target external system.
While phases handle the lifecycle of promotion, sources interface with your resource types and sources of truth to perform relevant transactions.

Currently, Glu has implementations for the following sources:

Expand All @@ -142,24 +142,24 @@ Currently, Glu has implementations for the following sources:
We look to add more in the not-so-distant future. However, these can also be implemented by hand via the following interfaces:

```go
// Controller is the core interface for resource sourcing and management.
// Phase is the core interface for resource sourcing and management.
// These types can be registered on pipelines and can depend upon on another for promotion.
type Controller interface {
type Phase interface {
Metadata() Metadata
Get(context.Context) (any, error)
Promote(context.Context) error
}

// ResourceController is a Controller bound to a Resource type R.
type ResourceController[R Resource] interface {
Controller
// ResourcePhase is a Phase bound to a Resource type R.
type ResourcePhase[R Resource] interface {
Phase
GetResource(context.Context) (R, error)
}
```

### Triggers

Schedule promotions to run automatically on an interval for controllers matching a specific set of labels:
Schedule promotions to run automatically on an interval for phases matching a specific set of labels:

```go
// schedule promotion attempts to staging every 10 seconds
Expand Down
Loading

0 comments on commit ac88c5a

Please sign in to comment.