Skip to content

Commit

Permalink
Adds ResourceGroupPathManifestReader
Browse files Browse the repository at this point in the history
  • Loading branch information
seans3 committed Sep 23, 2020
1 parent f91e682 commit 83a394b
Show file tree
Hide file tree
Showing 3 changed files with 292 additions and 0 deletions.
88 changes: 88 additions & 0 deletions internal/live/rgpath.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package live

import (
"fmt"
"strings"

"github.com/GoogleContainerTools/kpt/pkg/kptfile/kptfileutil"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/klog"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/manifestreader"
)

// ResourceGroupPathManifestReader encapsulates the default path
// manifest reader.
type ResourceGroupPathManifestReader struct {
pathReader *manifestreader.PathManifestReader
}

// Read reads the manifests and returns them as Info objects.
// Generates and adds a ResourceGroup inventory object from
// Kptfile data.
func (p *ResourceGroupPathManifestReader) Read() ([]*resource.Info, error) {
// Using the default path reader to generate the objects.
infos, err := p.pathReader.Read()
if err != nil {
return []*resource.Info{}, err
}
// Read the Kptfile in the top directory to get the inventory
// parameters to create the ResourceGroup inventory object.
kf, err := kptfileutil.ReadFile(p.pathReader.Path)
if err != nil {
return []*resource.Info{}, err
}
inv := kf.Inventory
klog.V(4).Infof("generating ResourceGroup inventory object %s/%s/%s", inv.Namespace, inv.Name, inv.InventoryID)
invInfo, err := generateInventoryObj(inv.Name, inv.Namespace, inv.InventoryID)
if err != nil {
return []*resource.Info{}, err
}
infos = append(infos, invInfo)
return infos, nil
}

// generateInventoryObj returns the ResourceGroupInventory object using the
// passed information.
func generateInventoryObj(name string, namespace string, id string) (*resource.Info, error) {
// Validate the parameters
name = strings.TrimSpace(name)
if name == "" {
return nil, fmt.Errorf("kptfile inventory empty name")
}
namespace = strings.TrimSpace(namespace)
if namespace == "" {
return nil, fmt.Errorf("kptfile inventory empty namespace")
}
id = strings.TrimSpace(id)
if id == "" {
return nil, fmt.Errorf("kptfile inventory missing inventoryID")
}
// Create and return ResourceGroup custom resource as inventory object.
var inventoryObj = unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "configmanagement.gke.io/v1beta1",
"kind": "ResourceGroup",
"metadata": map[string]interface{}{
"name": name,
"namespace": namespace,
"labels": map[string]interface{}{
common.InventoryLabel: id,
},
},
"spec": map[string]interface{}{
"resources": []interface{}{},
},
},
}
var invInfo = &resource.Info{
Namespace: namespace,
Name: name,
Object: &inventoryObj,
}
return invInfo, nil
}
192 changes: 192 additions & 0 deletions internal/live/rgpath_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package live

import (
"fmt"
"io/ioutil"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/resource"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/manifestreader"
)

var (
inventoryNamespace = "test-namespace"
inventoryName = "inventory-obj-name"
inventoryID = "XXXXXXXXXX-FOOOOOO"
)

var kptFile = `
apiVersion: kpt.dev/v1alpha1
kind: Kptfile
metadata:
name: test1
upstream:
type: git
git:
commit: 786b898857bd7e9647c229d5f39b0be4de86c915
repo: git@github.com:seans3/blueprint-helloworld
directory: /
ref: master
inventory:
namespace: test-namespace
name: inventory-obj-name
inventoryID: XXXXXXXXXX-FOOOOOO
`

var kptFileMissingID = `
apiVersion: kpt.dev/v1alpha1
kind: Kptfile
metadata:
name: test1
upstream:
type: git
git:
commit: 786b898857bd7e9647c229d5f39b0be4de86c915
repo: git@github.com:seans3/blueprint-helloworld
directory: /
ref: master
inventory:
namespace: test-namespace
name: inventory-obj-name
`

var podA = `
apiVersion: v1
kind: Pod
metadata:
name: pod-a
namespace: test-namespace
labels:
name: test-pod-label
spec:
containers:
- name: kubernetes-pause
image: k8s.gcr.io/pause:2.0
`

var deploymentA = `
kind: Deployment
apiVersion: apps/v1
metadata:
name: test-deployment
spec:
replicas: 1
`

func TestInvGenPathManifestReader_Read(t *testing.T) {
testCases := map[string]struct {
manifests map[string]string
numInfos int
isError bool
}{
"Kptfile missing inventory id is error": {
manifests: map[string]string{
"Kptfile": kptFileMissingID,
"pod-a.yaml": podA,
},
numInfos: 0,
isError: true,
},
"Basic ResourceGroup inventory object created": {
manifests: map[string]string{
"Kptfile": kptFile,
"pod-a.yaml": podA,
},
numInfos: 2,
isError: false,
},
"ResourceGroup inventory object created, multiple objects": {
manifests: map[string]string{
"Kptfile": kptFile,
"pod-a.yaml": podA,
"deployment-a.yaml": deploymentA,
},
numInfos: 3,
isError: false,
},
"ResourceGroup inventory object created, Kptfile last": {
manifests: map[string]string{
"deployment-a.yaml": deploymentA,
"Kptfile": kptFile,
},
numInfos: 2,
isError: false,
},
}

for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test-ns")
defer tf.Cleanup()

// Set up the yaml manifests (including Kptfile) in temp dir.
dir, err := ioutil.TempDir("", "path-reader-test")
assert.NoError(t, err)
for filename, content := range tc.manifests {
p := filepath.Join(dir, filename)
err := ioutil.WriteFile(p, []byte(content), 0600)
assert.NoError(t, err)
}

// Create the ResourceGroupPathManifestReader, and Read()
// the manifests into infos.
pathReader := &manifestreader.PathManifestReader{
Path: dir,
ReaderOptions: manifestreader.ReaderOptions{
Factory: tf,
Namespace: inventoryNamespace,
EnforceNamespace: false,
},
}
rgPathReader := &ResourceGroupPathManifestReader{
pathReader: pathReader,
}
readInfos, err := rgPathReader.Read()

// Validate the returned values are correct.
if tc.isError {
if err == nil {
t.Fatalf("expected error but received none")
}
return
}
assert.NoError(t, err)
assert.Equal(t, len(readInfos), tc.numInfos)
for _, info := range readInfos {
assert.Equal(t, inventoryNamespace, info.Namespace)
}
invInfo, _, err := inventory.SplitInfos(readInfos)
assert.NoError(t, err)
assert.Equal(t, inventoryName, invInfo.Name)
actualID, err := getInventoryLabel(invInfo)
assert.NoError(t, err)
assert.Equal(t, inventoryID, actualID)
})
}
}

func getInventoryLabel(inv *resource.Info) (string, error) {
obj := inv.Object
if obj == nil {
return "", fmt.Errorf("inventory object is nil")
}
accessor, err := meta.Accessor(obj)
if err != nil {
return "", err
}
labels := accessor.GetLabels()
inventoryLabel, exists := labels[common.InventoryLabel]
if !exists {
return "", fmt.Errorf("inventory label does not exist for inventory object: %s", common.InventoryLabel)
}
return inventoryLabel, nil
}
12 changes: 12 additions & 0 deletions pkg/kptfile/pkgfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ type KptFile struct {

// Functions contains configuration for running functions
Functions Functions `yaml:"functions,omitempty"`

// Parameters for inventory object.
Inventory Inventory `yaml:"inventory,omitempty"`
}

// Inventory encapsulates the parameters for the inventory object. All of the
// the parameters are required if any are set.
type Inventory struct {
Namespace string `yaml:"namespace,omitempty"`
Name string `yaml:"name,omitempty"`
// Unique label to identify inventory object in cluster.
InventoryID string `yaml:"inventoryID,omitempty"`
}

type Functions struct {
Expand Down

0 comments on commit 83a394b

Please sign in to comment.