Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AgentAction controller #73

Merged
merged 6 commits into from
Mar 11, 2022
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
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Setup Porter
uses: getporter/[email protected]
with:
porter_version: v1.0.0-alpha.8
porter_version: v1.0.0-alpha.13
- name: Set up Mage
run: go run mage.go EnsureMage
- name: Test
Expand All @@ -48,4 +48,4 @@ jobs:
if: ${{ github.event_name != 'pull_request' }}
run: mage -v Publish
env:
ENV: production
PORTER_ENV: production
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ operator
!vendor/**/zz_generated.*
/manifests.yaml
installer/manifests/operator.yaml
installer/porter.yaml

# editor and IDE paraphernalia
.env
Expand Down
14 changes: 13 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [Makefile explained](#makefile-explained)
* [Run a test installation](#run-a-test-installation)
* [Modify the porter agent](#modify-the-porter-agent)
* [Publish to another registry](#publish-to-another-registry)
---

# New Contributor Guide
Expand Down Expand Up @@ -63,7 +64,7 @@ These are targets that you won't usually run directly, other targets use them as
* **DeleteTestCluster** deletes the KIND cluster named porter.
* **Clean** deletes all data from the test cluster.
* **CleanManual** removes all
* **CleanTests** removes any namespaces created by the test suite (where porter-test=true).
* **CleanTests** removes any namespaces created by the test suite (with label porter.sh/testdata=true).

## Modify the porter agent

Expand Down Expand Up @@ -147,3 +148,14 @@ kubectl get pods -n test --wait
# Now you can see the result in porter!
porter logs hello -n operator
```

## Publish to Another Registry

When working on the operator, it can be helpful to publish the operator's bundle to another registry instead of the default localhost:5000.
For example, when you are testing the operator on a real cluster instead of KinD.

```
export PORTER_ENV=custom # this can be anything but production or test
export PORTER_OPERATOR_REGISTRY=ghcr.io/getporter/test # Set this to a registry for which you have push access
mage publish
```
12 changes: 6 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# syntax=docker/dockerfile:1.2
# syntax=docker/dockerfile:1.4

# Build the manager binary
FROM golang:1.17 as builder
Expand All @@ -14,18 +14,18 @@ RUN --mount=type=cache,target=/go/pkg/mod \

# Copy the go source
COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/
COPY --link api/ api/
COPY --link controllers/ controllers/

# Build
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o manager main.go

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
FROM gcr.io/distroless/static
WORKDIR /app
COPY --from=builder /workspace/manager .
USER 65532:65532
COPY --from=builder --chown=65532.0 /workspace/manager .
USER 65532

ENTRYPOINT ["/app/manager"]
3 changes: 3 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ resources:
- crdVersion: v1
kind: PorterConfig
version: v1
- crdVersion: v1
kind: AgentAction
version: v1
version: 3-alpha
plugins:
manifests.sdk.operatorframework.io/v2: {}
Expand Down
40 changes: 40 additions & 0 deletions api/v1/agent_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package v1

// AgentPhase are valid statuses of a Porter agent job
// that is managing a change to a Porter resource.
type AgentPhase string

const (
// PhaseUnknown means that we don't know what porter is doing yet.
PhaseUnknown AgentPhase = "Unknown"

// PhasePending means that Porter's execution is pending.
PhasePending AgentPhase = "Pending"

// PhaseRunning indicates that Porter is running.
PhaseRunning AgentPhase = "Running"

// PhaseSucceeded means that calling Porter succeeded.
PhaseSucceeded AgentPhase = "Succeeded"

// PhaseFailed means that calling Porter failed.
PhaseFailed AgentPhase = "Failed"
)

// AgentConditionType are valid conditions of a Porter agent job
// that is managing a change to a Porter resource.
type AgentConditionType string

const (
// ConditionScheduled means that the Porter agent has been scheduled.
ConditionScheduled AgentConditionType = "Scheduled"

// ConditionStarted means that the Porter agent has started.
ConditionStarted AgentConditionType = "Started"

// ConditionComplete means the Porter agent has completed successfully.
ConditionComplete AgentConditionType = "Completed"

// ConditionFailed means the Porter agent failed.
ConditionFailed AgentConditionType = "Failed"
)
100 changes: 100 additions & 0 deletions api/v1/agentaction_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package v1

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// AgentActionSpec defines the desired state of AgentAction
type AgentActionSpec struct {
// AgentConfig is the name of an AgentConfig to use instead of the AgentConfig defined at the namespace or system level.
// +optional
AgentConfig *corev1.LocalObjectReference `json:"agentConfig,omitempty"`

// PorterConfig is the name of a PorterConfig to use instead of the PorterConfig defined at the namespace or system level.
PorterConfig *corev1.LocalObjectReference `json:"porterConfig,omitempty"`

// Command to run inside the Porter Agent job. Defaults to running the agent.
Command []string `json:"command,omitempty"`

// Args to pass to the Porter Agent job. This should be the porter command that you want to run.
Args []string `json:"args,omitempty"`

// Files that should be present in the working directory where the command is run.
Files map[string][]byte `json:"files,omitempty"`

// Env variables to set on the Porter Agent job.
Env []corev1.EnvVar `json:"env,omitempty"`

// EnvFrom allows setting environment variables on the Porter Agent job, using secrets or config maps as the source.
EnvFrom []corev1.EnvFromSource `json:"envFrom,omitempty"`

// VolumeMounts that should be defined on the Porter Agent job.
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`

// Volumes that should be defined on the Porter Agent job.
Volumes []corev1.Volume `json:"volumes,omitempty"`
}

// AgentActionStatus defines the observed state of AgentAction
type AgentActionStatus struct {
// The last generation observed by the controller.
ObservedGeneration int64 `json:"observedGeneration,omitempty"`

// The currently active job that is running the Porter Agent.
Job *corev1.LocalObjectReference `json:"job,omitempty"`

// The current status of the agent.
// Possible values are: Unknown, Pending, Running, Succeeded, and Failed.
// +kubebuilder:validation:Type=string
Phase AgentPhase `json:"phase,omitempty"`

// Conditions store a list of states that have been reached.
// Each condition refers to the status of the Job
// Possible conditions are: Scheduled, Started, Completed, and Failed
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status

// AgentAction is the Schema for the agentactions API
type AgentAction struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec AgentActionSpec `json:"spec,omitempty"`
Status AgentActionStatus `json:"status,omitempty"`
}

func (a *AgentAction) GetConditions() *[]metav1.Condition {
return &a.Status.Conditions
}

// GetRetryLabelValue returns a value that is safe to use
// as a label value and represents the retry annotation used
// to trigger reconciliation.
func (a *AgentAction) GetRetryLabelValue() string {
return getRetryLabelValue(a.Annotations)
}

// SetRetryAnnotation flags the resource to retry its last operation.
func (a *AgentAction) SetRetryAnnotation(retry string) {
if a.Annotations == nil {
a.Annotations = make(map[string]string, 1)
}
a.Annotations[AnnotationRetry] = retry
}

// +kubebuilder:object:root=true

// AgentActionList contains a list of AgentAction
type AgentActionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []AgentAction `json:"items"`
}

func init() {
SchemeBuilder.Register(&AgentAction{}, &AgentActionList{})
}
13 changes: 13 additions & 0 deletions api/v1/agentaction_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package v1

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestAgentAction_SetRetryAnnotation(t *testing.T) {
action := AgentAction{}
action.SetRetryAnnotation("retry-1")
assert.Equal(t, "retry-1", action.Annotations[AnnotationRetry])
}
6 changes: 2 additions & 4 deletions api/v1/agentconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,11 @@ func (c AgentConfigSpec) GetPorterImage() string {
version := c.PorterVersion
if version == "" {
// We don't use a mutable tag like latest, or canary because it's a bad practice that we don't want to encourage.
// As we test out the operator with new versions of Porter, keep this value up-to-date so that the default
// version is guaranteed to work.
version = "v1.0.0-alpha.8"
version = DefaultPorterAgentVersion
}
repo := c.PorterRepository
if repo == "" {
repo = "ghcr.io/getporter/porter-agent"
repo = DefaultPorterAgentRepository
}

if digest, err := digest.Parse(version); err == nil {
Expand Down
8 changes: 4 additions & 4 deletions api/v1/agentconfig_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ import (
func TestAgentConfigSpec_GetPorterImage(t *testing.T) {
t.Run("default", func(t *testing.T) {
c := AgentConfigSpec{}
assert.Equal(t, "ghcr.io/getporter/porter-agent:v1.0.0-alpha.8", c.GetPorterImage())
assert.Equal(t, DefaultPorterAgentRepository+":"+DefaultPorterAgentVersion, c.GetPorterImage())
})

t.Run("porter version set", func(t *testing.T) {
c := AgentConfigSpec{PorterVersion: "canary"}
assert.Equal(t, "ghcr.io/getporter/porter-agent:canary", c.GetPorterImage())
assert.Equal(t, DefaultPorterAgentRepository+":canary", c.GetPorterImage())
})

t.Run("porter repository set", func(t *testing.T) {
// Test if someone has mirrored porter's agent to another registry
c := AgentConfigSpec{PorterRepository: "localhost:5000/myporter"}
assert.Equal(t, "localhost:5000/myporter:v1.0.0-alpha.8", c.GetPorterImage())
assert.Equal(t, "localhost:5000/myporter:"+DefaultPorterAgentVersion, c.GetPorterImage())
})

t.Run("porter repository and version set", func(t *testing.T) {
Expand All @@ -35,7 +35,7 @@ func TestAgentConfigSpec_GetPorterImage(t *testing.T) {
c := AgentConfigSpec{
PorterVersion: "sha256:ea7d328dc6b65e4b62a971ba8436f89d5857c2878c211312aaa5e2db2e47a2da",
}
assert.Equal(t, "ghcr.io/getporter/porter-agent@sha256:ea7d328dc6b65e4b62a971ba8436f89d5857c2878c211312aaa5e2db2e47a2da", c.GetPorterImage())
assert.Equal(t, DefaultPorterAgentRepository+"@sha256:ea7d328dc6b65e4b62a971ba8436f89d5857c2878c211312aaa5e2db2e47a2da", c.GetPorterImage())
})
}

Expand Down
92 changes: 92 additions & 0 deletions api/v1/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package v1

const (
// DefaultPorterAgentRepository is the default image repository of the Porter
// Agent to use when it is not configured in the operator.
DefaultPorterAgentRepository = "ghcr.io/getporter/porter-agent"

// DefaultPorterAgentVersion is the default version of the Porter Agent to
// use when it is not configured in the operator.
//
// As we test out the operator with new versions of Porter, keep this value
// up-to-date so that the default version is guaranteed to work.
DefaultPorterAgentVersion = "v1.0.0-alpha.13"

// LabelJobType is a label applied to jobs created by the operator. It
// indicates the purpose of the job.
LabelJobType = Prefix + "jobType"

// JobTypeAgent is the value of job type label applied to the Porter Agent.
JobTypeAgent = "porter-agent"

// JobTypeInstaller is the value of the job type label applied to the job
// that runs the bundle.
JobTypeInstaller = "bundle-installer"

// LabelSecretType is a label applied to secrets created by the operator. It
// indicates the purpose of the secret.
LabelSecretType = Prefix + "secretType"

// SecretTypeConfig is the value of the secret type label applied to the
// secret that contains files to copy into the porter home directory.
SecretTypeConfig = "porter-config"

// SecretTypeWorkdir is the value of the secret type label applied to the
// secret that contains files to copy into the working directory of the
// Porter Agent.
SecretTypeWorkdir = "workdir"

// LabelManaged is a label applied to resources created by the Porter
// Operator.
LabelManaged = Prefix + "managed"

// LabelResourceKind is a label applied to resources created by the Porter
// Operator, representing the kind of owning resource. It is used to help the
// operator determine if a resource has already been created.
LabelResourceKind = Prefix + "resourceKind"

// LabelResourceName is a label applied to the resources created by the
// Porter Operator, representing the name of the owning resource. It is used
// to help the operator determine if a resource has
// already been created.
LabelResourceName = Prefix + "resourceName"

// LabelResourceGeneration is a label applied to the resources created by the
// Porter Operator, representing the generation of the owning resource. It is
// used to help the operator determine if a resource has
// already been created.
LabelResourceGeneration = Prefix + "resourceGeneration"

// LabelRetry is a label applied to the resources created by the
// Porter Operator, representing the retry attempt identifier.
LabelRetry = Prefix + "retry"

// FinalizerName is the name of the finalizer applied to Porter Operator
// resources that should be reconciled by the operator before allowing it to
// be deleted.
FinalizerName = Prefix + "finalizer"

// VolumePorterSharedName is the name of the volume shared between the porter
// agent and the invocation image.
VolumePorterSharedName = "porter-shared"

// VolumePorterSharedPath is the mount path of the volume shared between the
// porter agent and the invocation image.
VolumePorterSharedPath = "/porter-shared"

// VolumePorterConfigName is the name of the volume that contains Porter's config
// file.
VolumePorterConfigName = "porter-config"

// VolumePorterConfigPath is the mount path of the volume containing Porter's
// config file.
VolumePorterConfigPath = "/porter-config"

// VolumePorterWorkDirName is the name of the volume that is used as the Porter's
// working directory.
VolumePorterWorkDirName = "porter-workdir"

// VolumePorterWorkDirPath is the mount path of the volume that is used as the
// Porter's working directory.
VolumePorterWorkDirPath = "/porter-workdir"
)
Loading