Skip to content

Commit ccebe3c

Browse files
author
Jeff McCormick
committed
add labels flag to create cluster, refactor lables templating
1 parent 0a2c792 commit ccebe3c

File tree

6 files changed

+355
-28
lines changed

6 files changed

+355
-28
lines changed

client/cmd/cluster.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"k8s.io/client-go/pkg/api/v1"
2727
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
2828
"strconv"
29+
"strings"
2930
"time"
3031
)
3132

@@ -210,6 +211,7 @@ func createCluster(args []string) {
210211

211212
// Create an instance of our TPR
212213
newInstance := getClusterParams(clusterName)
214+
validateConfigPolicies()
213215

214216
newInstance.Spec.PSW_LAST_UPDATE = time.Now()
215217

@@ -277,6 +279,7 @@ func getClusterParams(name string) *tpr.PgCluster {
277279
spec.REPLICAS = "0"
278280
spec.STRATEGY = "1"
279281
spec.NodeName = NodeName
282+
spec.UserLabels = UserLabelsMap
280283

281284
//override any values from config file
282285
str := viper.GetString("CLUSTER.PORT")
@@ -394,6 +397,24 @@ func getValidNodeName() string {
394397
return "error here"
395398

396399
}
400+
func validateUserLabels() error {
401+
402+
var err error
403+
labels := strings.Split(UserLabels, ",")
404+
405+
for _, v := range labels {
406+
fmt.Printf("%s\n", v)
407+
p := strings.Split(v, "=")
408+
if len(p) < 2 {
409+
return errors.New("invalid labels format")
410+
} else {
411+
UserLabelsMap[p[0]] = p[1]
412+
}
413+
}
414+
return err
415+
416+
}
417+
397418
func validateNodeName(nodeName string) error {
398419
var err error
399420
lo := meta_v1.ListOptions{}

client/cmd/create.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ var Password string
2626
var SecretFrom, BackupPath, BackupPVC string
2727
var PoliciesFlag, PolicyFile, PolicyURL string
2828
var NodeName string
29+
var UserLabels string
30+
var UserLabelsMap map[string]string
2931
var Series int
3032

3133
// CreateCmd represents the create command
@@ -80,6 +82,14 @@ pgo create cluster mycluster`,
8082

8183
}
8284

85+
if UserLabels != "" {
86+
err = validateUserLabels()
87+
if err != nil {
88+
log.Error("invalid user labels, check --labels value")
89+
return
90+
}
91+
}
92+
8393
if len(args) == 0 {
8494
log.Error("a cluster name is required for this command")
8595
} else {
@@ -124,11 +134,13 @@ func init() {
124134
createClusterCmd.Flags().StringVarP(&Password, "password", "w", "", "The password to use for initial database users")
125135
createClusterCmd.Flags().StringVarP(&SecretFrom, "secret-from", "s", "", "The cluster name to use when restoring secrets")
126136
createClusterCmd.Flags().StringVarP(&BackupPVC, "backup-pvc", "p", "", "The backup archive PVC to restore from")
137+
createClusterCmd.Flags().StringVarP(&UserLabels, "labels", "l", "", "The labels to apply to this cluster")
127138
createClusterCmd.Flags().StringVarP(&BackupPath, "backup-path", "x", "", "The backup archive path to restore from")
128139
createClusterCmd.Flags().StringVarP(&PoliciesFlag, "policies", "z", "", "The policies to apply when creating a cluster, comma separated")
129140
createClusterCmd.Flags().StringVarP(&CCP_IMAGE_TAG, "ccp-image-tag", "c", "", "The CCP_IMAGE_TAG to use for cluster creation, if specified overrides the .pgo.yaml setting")
130141
createClusterCmd.Flags().IntVarP(&Series, "series", "e", 1, "The number of clusters to create in a series, defaults to 1")
131142
createPolicyCmd.Flags().StringVarP(&PolicyURL, "url", "u", "", "The url to use for adding a policy")
132143
createPolicyCmd.Flags().StringVarP(&PolicyFile, "in-file", "i", "", "The policy file path to use for adding a policy")
144+
UserLabelsMap = make(map[string]string)
133145

134146
}

client/cmd/label.go

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
/*
2+
Copyright 2017 Crunchy Data Solutions, Inc.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
*/
15+
16+
package cmd
17+
18+
import (
19+
"fmt"
20+
log "github.com/Sirupsen/logrus"
21+
"k8s.io/apimachinery/pkg/labels"
22+
"k8s.io/client-go/rest"
23+
//"github.com/crunchydata/postgres-operator/operator/util"
24+
"encoding/json"
25+
"github.com/crunchydata/postgres-operator/tpr"
26+
jsonpatch "github.com/evanphx/json-patch"
27+
"github.com/spf13/cobra"
28+
"k8s.io/apimachinery/pkg/api/meta"
29+
"k8s.io/client-go/kubernetes"
30+
//"github.com/spf13/viper"
31+
//"io/ioutil"
32+
//kerrors "k8s.io/apimachinery/pkg/api/errors"
33+
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34+
"k8s.io/apimachinery/pkg/types"
35+
"k8s.io/client-go/pkg/apis/extensions/v1beta1"
36+
//"os/user"
37+
"strings"
38+
)
39+
40+
var LabelCmdLabel string
41+
var DeleteLabel bool
42+
43+
var labelCmd = &cobra.Command{
44+
Use: "label",
45+
Short: "label a set of clusters",
46+
Long: `LABEL allows you to add or remove a label on a set of clusters
47+
For example:
48+
49+
pgo label mycluster yourcluster --label=environment=prod
50+
pgo label mycluster yourcluster --label=environment=prod --delete-label
51+
pgo label --label=environment=prod --selector=name=mycluster
52+
pgo label --label=environment=prod --selector=status=final --dry-run
53+
.`,
54+
Run: func(cmd *cobra.Command, args []string) {
55+
log.Debug("label called")
56+
if len(args) == 0 && Selector == "" {
57+
log.Error("selector or list of clusters is required to label a policy")
58+
return
59+
}
60+
if LabelCmdLabel == "" {
61+
log.Error(`You must specify the label to apply.`)
62+
} else {
63+
labelClusters(args)
64+
}
65+
},
66+
}
67+
68+
func init() {
69+
RootCmd.AddCommand(labelCmd)
70+
71+
labelCmd.Flags().StringVarP(&Selector, "selector", "s", "", "The selector to use for cluster filtering ")
72+
labelCmd.Flags().StringVarP(&LabelCmdLabel, "label", "l", "", "The new label to apply for any selected or specified clusters")
73+
labelCmd.Flags().BoolVarP(&DryRun, "dry-run", "d", false, "--dry-run shows clusters that label would be applied to but does not actually label them")
74+
labelCmd.Flags().BoolVarP(&DeleteLabel, "delete-label", "x", false, "--delete-label deletes a label from matching clusters")
75+
76+
}
77+
78+
func labelClusters(clusters []string) {
79+
var err error
80+
81+
if len(clusters) == 0 && Selector == "" {
82+
fmt.Println("no clusters specified")
83+
return
84+
}
85+
//get filtered list of pgcluster tprs
86+
//get a list of all clusters
87+
clusterList := tpr.PgClusterList{}
88+
myselector := labels.Everything()
89+
if Selector != "" {
90+
log.Debug("selector is " + Selector)
91+
myselector, err = labels.Parse(Selector)
92+
if err != nil {
93+
log.Error("could not parse --selector value " + err.Error())
94+
return
95+
}
96+
97+
log.Debugf("label selector is [%v]\n", myselector)
98+
err = Tprclient.Get().
99+
Resource(tpr.CLUSTER_RESOURCE).
100+
Namespace(Namespace).
101+
LabelsSelectorParam(myselector).
102+
Do().
103+
Into(&clusterList)
104+
if err != nil {
105+
log.Error("error getting list of clusters" + err.Error())
106+
return
107+
}
108+
if len(clusterList.Items) == 0 {
109+
fmt.Println("no clusters found")
110+
return
111+
}
112+
} else {
113+
//each arg represents a cluster name or the special 'all' value
114+
items := make([]tpr.PgCluster, 0)
115+
for _, cluster := range clusters {
116+
result := tpr.PgCluster{}
117+
err := Tprclient.Get().
118+
Resource(tpr.CLUSTER_RESOURCE).
119+
Namespace(Namespace).
120+
Name(cluster).
121+
Do().
122+
Into(&result)
123+
if err != nil {
124+
log.Error("error getting list of clusters" + err.Error())
125+
return
126+
}
127+
fmt.Println(result.Spec.Name)
128+
items = append(items, result)
129+
}
130+
clusterList.Items = items
131+
}
132+
133+
addLabels(clusterList.Items)
134+
135+
}
136+
137+
func addLabels(items []tpr.PgCluster) {
138+
for i := 0; i < len(items); i++ {
139+
fmt.Println("adding label to " + items[i].Spec.Name)
140+
if DryRun {
141+
fmt.Println("dry run only")
142+
} else {
143+
err := PatchPgCluster(Tprclient, LabelCmdLabel, items[i], Namespace)
144+
if err != nil {
145+
log.Error(err.Error())
146+
}
147+
}
148+
}
149+
150+
for i := 0; i < len(items); i++ {
151+
//get deployments for this TPR
152+
lo := meta_v1.ListOptions{LabelSelector: "pg-cluster=" + items[i].Spec.Name}
153+
deployments, err := Clientset.ExtensionsV1beta1().Deployments(Namespace).List(lo)
154+
if err != nil {
155+
log.Error("error getting list of deployments" + err.Error())
156+
return
157+
}
158+
159+
newLabels := make(map[string]string)
160+
for _, d := range deployments.Items {
161+
//update Deployment with the label
162+
//fmt.Println(TREE_BRANCH + "deployment : " + d.ObjectMeta.Name)
163+
if DryRun {
164+
} else {
165+
err := updateLabels(&d, Clientset, items[i].Spec.Name, Namespace, newLabels)
166+
if err != nil {
167+
log.Error(err.Error())
168+
}
169+
}
170+
}
171+
172+
}
173+
}
174+
175+
func updateLabels(deployment *v1beta1.Deployment, clientset *kubernetes.Clientset, clusterName string, namespace string, newLabels map[string]string) error {
176+
177+
var err error
178+
179+
var patchBytes, newData, origData []byte
180+
origData, err = json.Marshal(deployment)
181+
if err != nil {
182+
return err
183+
}
184+
185+
accessor, err2 := meta.Accessor(deployment)
186+
if err2 != nil {
187+
return err2
188+
}
189+
190+
objLabels := accessor.GetLabels()
191+
if objLabels == nil {
192+
objLabels = make(map[string]string)
193+
}
194+
195+
//update the deployment labels
196+
for key, value := range newLabels {
197+
objLabels[key] = value
198+
}
199+
log.Debugf("updated labels are %v\n", objLabels)
200+
201+
accessor.SetLabels(objLabels)
202+
203+
newData, err = json.Marshal(deployment)
204+
if err != nil {
205+
return err
206+
}
207+
208+
patchBytes, err = jsonpatch.CreateMergePatch(origData, newData)
209+
if err != nil {
210+
return err
211+
}
212+
213+
_, err = clientset.ExtensionsV1beta1().Deployments(namespace).Patch(clusterName, types.MergePatchType, patchBytes, "")
214+
if err != nil {
215+
log.Debug("error patching deployment " + err.Error())
216+
}
217+
return err
218+
219+
}
220+
221+
func PatchPgCluster(tprclient *rest.RESTClient, newLabel string, oldTpr tpr.PgCluster, namespace string) error {
222+
223+
fields := strings.Split(newLabel, "=")
224+
labelKey := fields[0]
225+
labelValue := fields[1]
226+
oldData, err := json.Marshal(oldTpr)
227+
if err != nil {
228+
return err
229+
}
230+
if oldTpr.Metadata.Labels == nil {
231+
oldTpr.Metadata.Labels = make(map[string]string)
232+
}
233+
oldTpr.Metadata.Labels[labelKey] = labelValue
234+
var newData, patchBytes []byte
235+
newData, err = json.Marshal(oldTpr)
236+
if err != nil {
237+
return err
238+
}
239+
patchBytes, err = jsonpatch.CreateMergePatch(oldData, newData)
240+
if err != nil {
241+
return err
242+
}
243+
244+
log.Debug(string(patchBytes))
245+
246+
_, err6 := tprclient.Patch(types.MergePatchType).
247+
Namespace(namespace).
248+
Resource(tpr.CLUSTER_RESOURCE).
249+
Name(oldTpr.Spec.Name).
250+
Body(patchBytes).
251+
Do().
252+
Get()
253+
254+
return err6
255+
256+
}

0 commit comments

Comments
 (0)