Skip to content

Commit 15a9957

Browse files
author
Amit Kumar Das
authored
feat(recipe): add labeling as an operation (#181)
This commit adds labeling as an operation. In other words, this lets one to labels against one or more resources. This also has tunable to unset labels against the resources that was applied previously. Signed-off-by: AmitKumarDas <[email protected]>
1 parent f01b6bd commit 15a9957

File tree

3 files changed

+299
-10
lines changed

3 files changed

+299
-10
lines changed

.github/workflows/test-release.yaml

+2-10
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,8 @@ jobs:
1111
steps:
1212
- name: Checkout Code
1313
uses: actions/checkout@v2
14-
- name: Setup GOPATH
15-
run: |
16-
echo "::set-env name=GOPATH::$(go env GOPATH)"
17-
echo "::add-path::$(go env GOPATH)/bin"
1814
- name: Setup Golang
19-
uses: actions/setup-go@v1
15+
uses: actions/setup-go@v2
2016
with:
2117
go-version: 1.13.5
2218
- run: make ${{ matrix.test }}
@@ -29,12 +25,8 @@ jobs:
2925
steps:
3026
- name: Checkout Code
3127
uses: actions/checkout@v2
32-
- name: Setup GOPATH
33-
run: |
34-
echo "::set-env name=GOPATH::$(go env GOPATH)"
35-
echo "::add-path::$(go env GOPATH)/bin"
3628
- name: Setup Golang
37-
uses: actions/setup-go@v1
29+
uses: actions/setup-go@v2
3830
with:
3931
go-version: 1.13.5
4032
- run: sudo make ${{ matrix.test }}

pkg/recipe/labeling.go

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
Copyright 2020 The MayaData Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package recipe
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/pkg/errors"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
25+
"k8s.io/apimachinery/pkg/labels"
26+
types "mayadata.io/d-operators/types/recipe"
27+
"openebs.io/metac/dynamic/clientset"
28+
)
29+
30+
// Labeling helps applying desired labels(s) against the resource
31+
type Labeling struct {
32+
BaseRunner
33+
Label *types.Label
34+
35+
result *types.LabelResult
36+
err error
37+
}
38+
39+
// LabelingConfig helps in creating new instance of Labeling
40+
type LabelingConfig struct {
41+
BaseRunner
42+
Label *types.Label
43+
}
44+
45+
// NewLabeler returns a new instance of Labeling
46+
func NewLabeler(config LabelingConfig) *Labeling {
47+
return &Labeling{
48+
BaseRunner: config.BaseRunner,
49+
Label: config.Label,
50+
result: &types.LabelResult{},
51+
}
52+
}
53+
54+
func (l *Labeling) unset(
55+
client *clientset.ResourceClient,
56+
obj *unstructured.Unstructured,
57+
) error {
58+
var currentLbls = obj.GetLabels()
59+
if len(currentLbls) == 0 ||
60+
len(currentLbls) < len(l.Label.ApplyLabels) {
61+
// given object is not eligible to be
62+
// unset, since all the desired labels
63+
// are not present
64+
return nil
65+
}
66+
for key, val := range l.Label.ApplyLabels {
67+
if currentLbls[key] != val {
68+
// given object is not eligible to be
69+
// unset, since it does not match the desired labels
70+
return nil
71+
}
72+
}
73+
var newLbls = map[string]string{}
74+
var isUnset bool
75+
for key, val := range currentLbls {
76+
isUnset = false
77+
for applyKey := range l.Label.ApplyLabels {
78+
if key == applyKey {
79+
// do not add this key & value
80+
// In other words unset this label
81+
isUnset = true
82+
break
83+
}
84+
}
85+
if !isUnset {
86+
// add existing key value pair since
87+
// this is not to be unset
88+
newLbls[key] = val
89+
}
90+
}
91+
// update the resource by removing desired labels
92+
obj.SetLabels(newLbls)
93+
// update the object against the cluster
94+
_, err := client.
95+
Namespace(obj.GetNamespace()).
96+
Update(
97+
obj,
98+
metav1.UpdateOptions{},
99+
)
100+
return err
101+
}
102+
103+
func (l *Labeling) label(
104+
client *clientset.ResourceClient,
105+
obj *unstructured.Unstructured,
106+
) error {
107+
var newLbls = map[string]string{}
108+
for key, val := range obj.GetLabels() {
109+
newLbls[key] = val
110+
}
111+
for nkey, nval := range l.Label.ApplyLabels {
112+
newLbls[nkey] = nval
113+
}
114+
// update the resource with new labels
115+
obj.SetLabels(newLbls)
116+
// update the object against the cluster
117+
_, err := client.
118+
Namespace(obj.GetNamespace()).
119+
Update(
120+
obj,
121+
metav1.UpdateOptions{},
122+
)
123+
return err
124+
}
125+
126+
func (l *Labeling) labelOrUnset(
127+
client *clientset.ResourceClient,
128+
obj *unstructured.Unstructured,
129+
) error {
130+
var isInclude bool
131+
for _, name := range l.Label.FilterByNames {
132+
if name == obj.GetName() {
133+
isInclude = true
134+
break
135+
}
136+
}
137+
if !isInclude && l.Label.AutoUnset {
138+
return l.unset(client, obj)
139+
}
140+
return l.label(client, obj)
141+
}
142+
143+
func (l *Labeling) labelAll() (*types.LabelResult, error) {
144+
var message = fmt.Sprintf(
145+
"Label resource %s %s: GVK %s",
146+
l.Label.State.GetNamespace(),
147+
l.Label.State.GetName(),
148+
l.Label.State.GroupVersionKind(),
149+
)
150+
err := l.Retry.Waitf(
151+
func() (bool, error) {
152+
// get appropriate dynamic client
153+
client, err := l.GetClientForAPIVersionAndKind(
154+
l.Label.State.GetAPIVersion(),
155+
l.Label.State.GetKind(),
156+
)
157+
if err != nil {
158+
return false, errors.Wrapf(
159+
err,
160+
"Failed to get resource client",
161+
)
162+
}
163+
items, err := client.
164+
Namespace(l.Label.State.GetNamespace()).
165+
List(metav1.ListOptions{
166+
LabelSelector: labels.Set(
167+
l.Label.State.GetLabels(),
168+
).String(),
169+
})
170+
if err != nil {
171+
return false, errors.Wrapf(
172+
err,
173+
"Failed to list resources",
174+
)
175+
}
176+
for _, obj := range items.Items {
177+
err := l.labelOrUnset(client, &obj)
178+
if err != nil {
179+
return false, err
180+
}
181+
}
182+
return true, nil
183+
},
184+
message,
185+
)
186+
if err != nil {
187+
return nil, err
188+
}
189+
return &types.LabelResult{
190+
Phase: types.LabelStatusPassed,
191+
Message: message,
192+
}, nil
193+
}
194+
195+
// Run applyies the desired labels or unsets them
196+
// against the resource(s)
197+
func (l *Labeling) Run() (*types.LabelResult, error) {
198+
return l.labelAll()
199+
}

types/recipe/label.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
Copyright 2020 The MayaData Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package types
18+
19+
import (
20+
"encoding/json"
21+
22+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23+
)
24+
25+
// Label represents the label apply operation against
26+
// one or more desired resources
27+
type Label struct {
28+
// Desired state i.e. resources that needs to be
29+
// labeled
30+
State *unstructured.Unstructured `json:"state"`
31+
32+
// Filter the resources by these names
33+
//
34+
// Optional
35+
FilterByNames []string `json:"filterByNames,omitempty"`
36+
37+
// ApplyLabels represents the labels that need to be
38+
// applied
39+
ApplyLabels map[string]string `json:"applyLabels"`
40+
41+
// AutoUnset removes the labels from the resources if
42+
// they were applied earlier and these resources are
43+
// no longer elgible to be applied with these labels
44+
//
45+
// Defaults to false
46+
AutoUnset bool `json:"autoUnset"`
47+
}
48+
49+
// String implements the Stringer interface
50+
func (l Label) String() string {
51+
raw, err := json.MarshalIndent(
52+
l,
53+
" ",
54+
".",
55+
)
56+
if err != nil {
57+
panic(err)
58+
}
59+
return string(raw)
60+
}
61+
62+
// LabelStatusPhase is a typed definition to determine the
63+
// result of executing the label operation
64+
type LabelStatusPhase string
65+
66+
const (
67+
// LabelStatusPassed defines a successful labeling
68+
LabelStatusPassed LabelStatusPhase = "Passed"
69+
70+
// LabelStatusWarning defines the label operation
71+
// that resulted in warnings
72+
LabelStatusWarning LabelStatusPhase = "Warning"
73+
74+
// LabelStatusFailed defines a failed labeling
75+
LabelStatusFailed LabelStatusPhase = "Failed"
76+
)
77+
78+
// ToTaskStatusPhase transforms ApplyStatusPhase to TestResultPhase
79+
func (phase LabelStatusPhase) ToTaskStatusPhase() TaskStatusPhase {
80+
switch phase {
81+
case LabelStatusPassed:
82+
return TaskStatusPassed
83+
case LabelStatusFailed:
84+
return TaskStatusFailed
85+
case LabelStatusWarning:
86+
return TaskStatusWarning
87+
default:
88+
return ""
89+
}
90+
}
91+
92+
// LabelResult holds the result of labeling operation
93+
type LabelResult struct {
94+
Phase LabelStatusPhase `json:"phase"`
95+
Message string `json:"message,omitempty"`
96+
Verbose string `json:"verbose,omitempty"`
97+
Warning string `json:"warning,omitempty"`
98+
}

0 commit comments

Comments
 (0)