-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathdeploymentusage.go
More file actions
176 lines (146 loc) · 5.65 KB
/
Copy pathdeploymentusage.go
File metadata and controls
176 lines (146 loc) · 5.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Package deploymentusage provides shared helpers for detecting whether a
// Codebase or CodebaseBranch is still referenced by CD pipeline deployment
// resources (CDPipeline, Stage). It backs the admission webhooks that block
// deletion of resources that are still in use.
package deploymentusage
import (
"context"
"fmt"
"strings"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
pipelineApi "github.com/epam/edp-cd-pipeline-operator/v2/api/v1"
)
// Kinds of the deployment resources that can hold a reference. Reference.String
// branches on these, so they are not free-form.
const (
KindCDPipeline = "CDPipeline"
KindStage = "Stage"
)
// Spec paths reported as Reference.Field, and in turn as metav1.StatusCause.Field.
// Clients key off these instead of parsing the message.
const (
FieldApplications = "spec.applications"
FieldApplicationsToPromote = "spec.applicationsToPromote"
FieldInputDockerStreams = "spec.inputDockerStreams"
FieldQualityGateAutotest = "spec.qualityGates.autotestName"
)
// Reference describes a single deployment resource that references a
// Codebase or CodebaseBranch, blocking its deletion.
type Reference struct {
Kind string
Name string
// Field is the JSON path holding the reference, e.g. "spec.inputDockerStreams".
// It is surfaced as the Field of a metav1.StatusCause.
Field string
// Reason is a human-readable phrase, e.g. "inputDockerStreams" or
// "autotest quality gate".
Reason string
// ParentCDPipeline is set only when Kind is KindStage.
ParentCDPipeline string
// Deleting is set when the referencing resource is terminating. It still
// blocks deletion; the denial advice switches from "remove" to "wait".
Deleting bool
}
// String renders the reference as it appears in the denial message shown to users.
func (r Reference) String() string {
reason := r.Reason
if r.Deleting {
reason += ", being deleted"
}
if r.Kind == KindStage {
return fmt.Sprintf("%s %s of %s %s (%s)", KindStage, r.Name, KindCDPipeline, r.ParentCDPipeline, reason)
}
return fmt.Sprintf("%s %s (%s)", r.Kind, r.Name, reason)
}
// Join renders references as a single description, in the form used by both the
// admission denial message and the stale-branch retention verdict.
func Join(refs []Reference) string {
descriptions := make([]string, 0, len(refs))
for _, ref := range refs {
descriptions = append(descriptions, ref.String())
}
return strings.Join(descriptions, "; ")
}
// ListCDPipelines returns every CDPipeline in the given namespace, terminating
// included: Stage finalizers read the CodebaseImageStreams until the pipeline
// is gone. When the CD pipeline CRDs are not installed in the cluster, it
// returns an empty, non-error result.
func ListCDPipelines(ctx context.Context, c client.Client, namespace string) ([]pipelineApi.CDPipeline, error) {
pipelines := &pipelineApi.CDPipelineList{}
if err := c.List(ctx, pipelines, client.InNamespace(namespace)); err != nil {
if IsKindUnavailable(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to list CDPipelines: %w", err)
}
return pipelines.Items, nil
}
// AutotestGate is a Stage quality gate that runs an autotest, together with the Reference
// describing the Stage holding it. Codebase and CodebaseBranch are both referenced through
// this one gate field and key on different parts of it.
type AutotestGate struct {
Reference
// AutotestName is the referenced Codebase name; never empty.
AutotestName string
// BranchName is the referenced git branch name, empty when the gate leaves it unset.
BranchName string
}
// FindAutotestGates returns every autotest quality gate of the namespace. Manual gates
// leave AutotestName unset and are dropped here, so no caller can mistake one for a
// match by comparing unset fields.
func FindAutotestGates(ctx context.Context, c client.Client, namespace string) ([]AutotestGate, error) {
stages, err := listActiveStages(ctx, c, namespace)
if err != nil {
return nil, err
}
var gates []AutotestGate
for i := range stages {
stage := &stages[i]
for _, gate := range stage.Spec.QualityGates {
if gate.AutotestName == nil || *gate.AutotestName == "" {
continue
}
gates = append(gates, AutotestGate{
Reference: Reference{
Kind: KindStage,
Name: stage.Name,
Field: FieldQualityGateAutotest,
Reason: "autotest quality gate",
ParentCDPipeline: stage.Spec.CdPipeline,
},
AutotestName: *gate.AutotestName,
BranchName: ptr.Deref(gate.BranchName, ""),
})
}
}
return gates, nil
}
// listActiveStages returns the Stages in the given namespace that are not
// being deleted. When the CD pipeline CRDs are not installed in the cluster,
// it returns an empty, non-error result.
func listActiveStages(ctx context.Context, c client.Client, namespace string) ([]pipelineApi.Stage, error) {
stages := &pipelineApi.StageList{}
if err := c.List(ctx, stages, client.InNamespace(namespace)); err != nil {
if IsKindUnavailable(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to list Stages: %w", err)
}
active := make([]pipelineApi.Stage, 0, len(stages.Items))
for i := range stages.Items {
if stages.Items[i].DeletionTimestamp != nil {
continue
}
active = append(active, stages.Items[i])
}
return active, nil
}
// IsKindUnavailable reports whether err indicates that a CRD kind is not
// registered/installed in the cluster (e.g. edp-cd-pipeline-operator is not
// deployed alongside this operator).
func IsKindUnavailable(err error) bool {
return apimeta.IsNoMatchError(err) || runtime.IsNotRegisteredError(err)
}