Skip to content

Commit 4b6d64b

Browse files
author
Eric Stroczynski
authored
internal/pkg/scaffold/olm-catalog: preserve certain fields, better CR/CRD updates (#1689)
* internal/pkg/scaffold/olm-catalog: preserve spec.customresourcedefinitions fields and use unique ID's for each owned CRD * internal/util/k8sutil: more robust GetTypeMetaFromBytes instead of GetKindFromYAML
1 parent 7104d8d commit 4b6d64b

File tree

3 files changed

+98
-40
lines changed

3 files changed

+98
-40
lines changed

internal/pkg/scaffold/olm-catalog/csv.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func getCSVFromFSIfExists(fs afero.Fs, path string) (*olmapiv1alpha1.ClusterServ
168168

169169
csv := &olmapiv1alpha1.ClusterServiceVersion{}
170170
if err := yaml.Unmarshal(csvBytes, csv); err != nil {
171-
return nil, false, errors.Wrap(err, path)
171+
return nil, false, errors.Wrapf(err, "error unmarshalling CSV %s", path)
172172
}
173173

174174
return csv, true, nil
@@ -336,28 +336,29 @@ func (s *CSV) updateCSVFromManifestFiles(cfg *CSVConfig, csv *olmapiv1alpha1.Clu
336336
scanner := yamlutil.NewYAMLScanner(yamlData)
337337
for scanner.Scan() {
338338
yamlSpec := scanner.Bytes()
339-
kind, err := k8sutil.GetKindfromYAML(yamlSpec)
339+
typemeta, err := k8sutil.GetTypeMetaFromBytes(yamlSpec)
340340
if err != nil {
341-
return errors.Wrap(err, f)
341+
return errors.Wrapf(err, "error getting type metadata from manifest %s", f)
342342
}
343-
found, err := store.AddToUpdater(yamlSpec, kind)
343+
found, err := store.AddToUpdater(yamlSpec, typemeta.Kind)
344344
if err != nil {
345-
return errors.Wrap(err, f)
345+
return errors.Wrapf(err, "error adding manifest %s to CSV updaters", f)
346346
}
347347
if !found {
348-
if _, ok := otherSpecs[kind]; !ok {
349-
otherSpecs[kind] = make([][]byte, 0)
348+
id := gvkID(typemeta.GroupVersionKind())
349+
if _, ok := otherSpecs[id]; !ok {
350+
otherSpecs[id] = make([][]byte, 0)
350351
}
351-
otherSpecs[kind] = append(otherSpecs[kind], yamlSpec)
352+
otherSpecs[id] = append(otherSpecs[id], yamlSpec)
352353
}
353354
}
354355
if err = scanner.Err(); err != nil {
355356
return err
356357
}
357358
}
358359

359-
for k := range store.crds.crKinds {
360-
if crSpecs, ok := otherSpecs[k]; ok {
360+
for id := range store.crds.crIDs {
361+
if crSpecs, ok := otherSpecs[id]; ok {
361362
for _, spec := range crSpecs {
362363
if err := store.AddCR(spec); err != nil {
363364
return err

internal/pkg/scaffold/olm-catalog/csv_updaters.go

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,20 @@ package catalog
1717
import (
1818
"bytes"
1919
"encoding/json"
20-
"fmt"
2120
"strings"
2221

2322
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
2423

2524
"github.com/ghodss/yaml"
2625
olmapiv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
2726
olminstall "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
27+
"github.com/pkg/errors"
2828
log "github.com/sirupsen/logrus"
2929
appsv1 "k8s.io/api/apps/v1"
3030
corev1 "k8s.io/api/core/v1"
3131
rbacv1 "k8s.io/api/rbac/v1"
3232
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
33+
"k8s.io/apimachinery/pkg/runtime/schema"
3334
)
3435

3536
// CSVUpdater is an interface for any data that can be in a CSV, which will be
@@ -207,7 +208,7 @@ func (u *InstallStrategyUpdate) Apply(csv *olmapiv1alpha1.ClusterServiceVersion)
207208
u.updateClusterPermissions(s)
208209
u.updateDeploymentSpecs(s)
209210
default:
210-
return fmt.Errorf("install strategy (%v) of unknown type", strat)
211+
return errors.Errorf("install strategy (%v) of unknown type", strat)
211212
}
212213

213214
// Re-serialize permissions into csv strategy.
@@ -240,40 +241,82 @@ func (u *InstallStrategyUpdate) updateDeploymentSpecs(strat *olminstall.Strategy
240241

241242
type CustomResourceDefinitionsUpdate struct {
242243
*olmapiv1alpha1.CustomResourceDefinitions
243-
crKinds map[string]struct{}
244+
crIDs map[string]struct{}
244245
}
245246

246247
func (store *updaterStore) AddOwnedCRD(yamlDoc []byte) error {
247248
crd := &apiextv1beta1.CustomResourceDefinition{}
248249
if err := yaml.Unmarshal(yamlDoc, crd); err != nil {
249250
return err
250251
}
251-
store.crds.Owned = append(store.crds.Owned, olmapiv1alpha1.CRDDescription{
252-
Name: crd.ObjectMeta.Name,
253-
Version: crd.Spec.Version,
254-
Kind: crd.Spec.Names.Kind,
255-
})
256-
store.crds.crKinds[crd.Spec.Names.Kind] = struct{}{}
252+
versions, err := getCRDVersions(crd)
253+
if err != nil {
254+
return errors.Wrapf(err, "failed to get owned CRD %s versions", crd.GetName())
255+
}
256+
for _, ver := range versions {
257+
kind := crd.Spec.Names.Kind
258+
crdDesc := olmapiv1alpha1.CRDDescription{
259+
Name: crd.ObjectMeta.Name,
260+
Version: ver,
261+
Kind: kind,
262+
}
263+
store.crds.crIDs[crdDescID(crdDesc)] = struct{}{}
264+
store.crds.Owned = append(store.crds.Owned, crdDesc)
265+
}
257266
return nil
258267
}
259268

269+
func getCRDVersions(crd *apiextv1beta1.CustomResourceDefinition) (versions []string, err error) {
270+
if len(crd.Spec.Versions) != 0 {
271+
for _, ver := range crd.Spec.Versions {
272+
// Only versions served by the API server are relevant to a CSV.
273+
if ver.Served {
274+
versions = append(versions, ver.Name)
275+
}
276+
}
277+
} else if crd.Spec.Version != "" {
278+
versions = append(versions, crd.Spec.Version)
279+
}
280+
if len(versions) == 0 {
281+
return nil, errors.Errorf("no versions in CRD %s", crd.GetName())
282+
}
283+
return versions, nil
284+
}
285+
286+
// crdDescID produces an opaque, unique string identifying a CRDDescription.
287+
func crdDescID(desc olmapiv1alpha1.CRDDescription) string {
288+
// Name should always be <lower kind>.<group>, so this is effectively a GVK.
289+
splitName := strings.Split(desc.Name, ".")
290+
return getGVKID(strings.Join(splitName[1:], "."), desc.Version, desc.Kind)
291+
}
292+
293+
// gvkID produces an opaque, unique string identifying a GVK.
294+
func gvkID(gvk schema.GroupVersionKind) string {
295+
return getGVKID(gvk.Group, gvk.Version, gvk.Kind)
296+
}
297+
298+
func getGVKID(g, v, k string) string {
299+
return g + v + k
300+
}
301+
260302
// Apply updates csv's "owned" CRDDescriptions. "required" CRDDescriptions are
261303
// left as-is, since they are user-defined values.
304+
// Apply will only make a new spec.customresourcedefinitions.owned element if
305+
// the CRD key is not in spec.customresourcedefinitions.owned already.
262306
func (u *CustomResourceDefinitionsUpdate) Apply(csv *olmapiv1alpha1.ClusterServiceVersion) error {
263307
set := make(map[string]olmapiv1alpha1.CRDDescription)
264-
for _, csvDesc := range csv.Spec.CustomResourceDefinitions.Owned {
265-
set[csvDesc.Name] = csvDesc
308+
for _, uDesc := range u.Owned {
309+
set[crdDescID(uDesc)] = uDesc
266310
}
267-
du := u.DeepCopy()
268-
for i, uDesc := range u.Owned {
269-
if csvDesc, ok := set[uDesc.Name]; ok {
270-
csvDesc.Name = uDesc.Name
271-
csvDesc.Version = uDesc.Version
272-
csvDesc.Kind = uDesc.Kind
273-
du.Owned[i] = csvDesc
311+
newDescs := []olmapiv1alpha1.CRDDescription{}
312+
for _, csvDesc := range csv.Spec.CustomResourceDefinitions.Owned {
313+
if uDesc, ok := set[crdDescID(csvDesc)]; !ok {
314+
newDescs = append(newDescs, uDesc)
315+
} else {
316+
newDescs = append(newDescs, csvDesc)
274317
}
275318
}
276-
csv.Spec.CustomResourceDefinitions.Owned = du.Owned
319+
csv.Spec.CustomResourceDefinitions.Owned = newDescs
277320
return nil
278321
}
279322

internal/util/k8sutil/k8sutil.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@
1515
package k8sutil
1616

1717
import (
18+
"bytes"
1819
"fmt"
20+
"io"
1921
"strings"
2022
"unicode"
2123

22-
"github.com/ghodss/yaml"
24+
"github.com/pkg/errors"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27+
"k8s.io/apimachinery/pkg/util/yaml"
2328
"k8s.io/client-go/rest"
2429
"k8s.io/client-go/tools/clientcmd"
2530
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
@@ -55,16 +60,6 @@ func GetKubeconfigAndNamespace(configPath string) (*rest.Config, string, error)
5560
return kubeconfig, namespace, nil
5661
}
5762

58-
func GetKindfromYAML(yamlData []byte) (string, error) {
59-
var temp struct {
60-
Kind string
61-
}
62-
if err := yaml.Unmarshal(yamlData, &temp); err != nil {
63-
return "", err
64-
}
65-
return temp.Kind, nil
66-
}
67-
6863
// GetDisplayName turns a project dir name in any of {snake, chain, camel}
6964
// cases, hierarchical dot structure, or space-delimited into a
7065
// space-delimited, title'd display name.
@@ -99,3 +94,22 @@ func GetDisplayName(name string) string {
9994
}
10095
return strings.TrimSpace(strings.Title(strings.Join(splitName, " ")))
10196
}
97+
98+
// GetTypeMetaFromBytes gets the type and object metadata from b. b is assumed
99+
// to be a single Kubernetes resource manifest.
100+
func GetTypeMetaFromBytes(b []byte) (t metav1.TypeMeta, err error) {
101+
u := unstructured.Unstructured{}
102+
r := bytes.NewReader(b)
103+
dec := yaml.NewYAMLOrJSONDecoder(r, 8)
104+
// There is only one YAML doc if there are no more bytes to be read or EOF
105+
// is hit.
106+
if err := dec.Decode(&u); err == nil && r.Len() != 0 {
107+
return t, errors.New("error getting TypeMeta from bytes: more than one manifest in b")
108+
} else if err != nil && err != io.EOF {
109+
return t, errors.Wrap(err, "error getting TypeMeta from bytes")
110+
}
111+
return metav1.TypeMeta{
112+
APIVersion: u.GetAPIVersion(),
113+
Kind: u.GetKind(),
114+
}, nil
115+
}

0 commit comments

Comments
 (0)