Skip to content

Commit 30ae069

Browse files
authored
Save inventory information into resourcegroup.yaml (#2615)
1 parent 35eb882 commit 30ae069

File tree

5 files changed

+340
-5
lines changed

5 files changed

+340
-5
lines changed

internal/cmdliveinit/cmdliveinit.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ package cmdliveinit
66
import (
77
"context"
88
"crypto/sha1"
9+
goerrors "errors"
910
"fmt"
11+
"io/ioutil"
1012
"os"
13+
"path/filepath"
1114
"strconv"
1215
"strings"
1316
"time"
@@ -16,14 +19,17 @@ import (
1619
"github.com/GoogleContainerTools/kpt/internal/errors"
1720
"github.com/GoogleContainerTools/kpt/internal/pkg"
1821
"github.com/GoogleContainerTools/kpt/internal/printer"
22+
"github.com/GoogleContainerTools/kpt/internal/types"
1923
"github.com/GoogleContainerTools/kpt/internal/util/attribution"
2024
kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
25+
rgfilev1alpha1 "github.com/GoogleContainerTools/kpt/pkg/api/resourcegroup/v1alpha1"
2126
"github.com/GoogleContainerTools/kpt/pkg/kptfile/kptfileutil"
2227
"github.com/spf13/cobra"
2328
"k8s.io/cli-runtime/pkg/genericclioptions"
2429
cmdutil "k8s.io/kubectl/pkg/cmd/util"
2530
"sigs.k8s.io/cli-utils/pkg/common"
2631
"sigs.k8s.io/cli-utils/pkg/config"
32+
"sigs.k8s.io/kustomize/kyaml/yaml"
2733
)
2834

2935
const defaultInventoryName = "inventory"
@@ -36,6 +42,24 @@ func (i *InvExistsError) Error() string {
3642
return "inventory information already set for package"
3743
}
3844

45+
// InvInRGExistsError defines new error when the inventory
46+
// values have already been set on the ResourceGroup file and we will warn
47+
// the user to migrate rather than init. This is part of kpt live STDIN work.
48+
type InvInRGExistsError struct{}
49+
50+
func (i *InvInRGExistsError) Error() string {
51+
return "inventory information already set for package"
52+
}
53+
54+
// InvInKfExistsError defines new error when the inventory
55+
// values have already been set on the Kptfile and we will warn
56+
// the user to migrate rather than init. This is part of kpt live STDIN work.
57+
type InvInKfExistsError struct{}
58+
59+
func (i *InvInKfExistsError) Error() string {
60+
return "inventory information already set within Kptfile for package"
61+
}
62+
3963
func NewRunner(ctx context.Context, factory cmdutil.Factory,
4064
ioStreams genericclioptions.IOStreams) *Runner {
4165
r := &Runner{
@@ -74,6 +98,7 @@ type Runner struct {
7498
Force bool // Set inventory values even if already set in Kptfile
7599
Name string // Inventory object name
76100
namespace string // Inventory object namespace
101+
RGFile string // resourcegroup object filepath
77102
InventoryID string // Inventory object unique identifier label
78103
Quiet bool // Output message during initialization
79104
}
@@ -105,6 +130,7 @@ func (r *Runner) runE(_ *cobra.Command, args []string) error {
105130
Quiet: r.Quiet,
106131
Name: r.Name,
107132
InventoryID: r.InventoryID,
133+
RGFileName: r.RGFile,
108134
Force: r.Force,
109135
}).Run(r.ctx)
110136
if err != nil {
@@ -122,12 +148,22 @@ type ConfigureInventoryInfo struct {
122148

123149
Name string
124150
InventoryID string
151+
RGFileName string
125152

126153
Force bool
127154
}
128155

129156
// Run updates the inventory info in the package given by the Path.
130157
func (c *ConfigureInventoryInfo) Run(ctx context.Context) error {
158+
// Use ResourceGroup file for inventory logic if the resourcegroup file
159+
// is set directly. For this feature gate, the resourcegroup must be directly set
160+
// through our tests since we are not exposing this through the command surface as a
161+
// flag, currently. When we promote this, the resourcegroup filename can be empty and
162+
// the default filename value will be inferred/used.
163+
if c.RGFileName != "" {
164+
return c.runLiveInitWithRGFile(ctx)
165+
}
166+
131167
const op errors.Op = "cmdliveinit.Run"
132168
pr := printer.FromContextOrDie(ctx)
133169

@@ -182,6 +218,119 @@ func (c *ConfigureInventoryInfo) Run(ctx context.Context) error {
182218
return nil
183219
}
184220

221+
// func runLiveInitWithRGFile is a modified version of ConfigureInventoryInfo.Run that stores the
222+
// package inventory information in a separate resourcegroup file. The logic for this is branched into
223+
// a separate function to enable feature gating.
224+
func (c *ConfigureInventoryInfo) runLiveInitWithRGFile(ctx context.Context) error {
225+
const op errors.Op = "cmdliveinit.runLiveInitWithRGFile"
226+
pr := printer.FromContextOrDie(ctx)
227+
228+
namespace, err := config.FindNamespace(c.Factory.ToRawKubeConfigLoader(), c.Pkg.UniquePath.String())
229+
if err != nil {
230+
return errors.E(op, c.Pkg.UniquePath, err)
231+
}
232+
namespace = strings.TrimSpace(namespace)
233+
if !c.Quiet {
234+
pr.Printf("initializing ResourceGroup inventory info (namespace: %s)...", namespace)
235+
}
236+
237+
// Autogenerate the name if it is not provided through the flag.
238+
if c.Name == "" {
239+
randomSuffix := common.RandomStr()
240+
c.Name = fmt.Sprintf("%s-%s", defaultInventoryName, randomSuffix)
241+
}
242+
243+
// Finally, create a ResourceGroup containing the inventory information.
244+
err = createRGFile(c.Pkg, &kptfilev1.Inventory{
245+
Namespace: namespace,
246+
Name: c.Name,
247+
InventoryID: c.InventoryID,
248+
}, c.RGFileName, c.Force)
249+
if !c.Quiet {
250+
if err == nil {
251+
pr.Printf("success\n")
252+
} else {
253+
pr.Printf("failed\n")
254+
}
255+
}
256+
if err != nil {
257+
return errors.E(op, c.Pkg.UniquePath, err)
258+
}
259+
// add metrics annotation to package resources to track the usage as the resources
260+
// will be applied using kpt live group
261+
at := attribution.Attributor{PackagePaths: []string{c.Pkg.UniquePath.String()}, CmdGroup: "live"}
262+
at.Process()
263+
return nil
264+
}
265+
266+
// createRGFile fills in the inventory object values into the resourcegroup object and writes to file storage.
267+
func createRGFile(p *pkg.Pkg, inv *kptfilev1.Inventory, filename string, force bool) error {
268+
const op errors.Op = "cmdliveinit.createRGFile"
269+
// Read the resourcegroup object io io.dir
270+
rg, err := p.ReadRGFile(filename)
271+
if err != nil && !goerrors.Is(err, os.ErrNotExist) {
272+
return errors.E(op, p.UniquePath, err)
273+
}
274+
275+
// Read the Kptfile to ensure that inventory information is not in Kptfile either.
276+
kf, err := p.Kptfile()
277+
if err != nil {
278+
return errors.E(op, p.UniquePath, err)
279+
}
280+
// Validate the inventory values don't exist in Kptfile.
281+
isEmpty := kptfileInventoryEmpty(kf.Inventory)
282+
if !isEmpty && !force {
283+
return errors.E(op, p.UniquePath, &InvInKfExistsError{})
284+
}
285+
// Set the Kptfile inventory to be nil if we force write to resourcegroup instead.
286+
kf.Inventory = nil
287+
288+
// Validate the inventory values don't already exist in Resourcegroup.
289+
if rg != nil && !force {
290+
return errors.E(op, p.UniquePath, &InvExistsError{})
291+
}
292+
// Initialize new resourcegroup object, as rg should have been nil.
293+
rg = &rgfilev1alpha1.ResourceGroup{ResourceMeta: rgfilev1alpha1.DefaultMeta}
294+
// // Finally, set the inventory parameters in the ResourceGroup object and write it.
295+
rg.Name = inv.Name
296+
rg.Namespace = inv.Namespace
297+
if inv.InventoryID != "" {
298+
rg.Labels = map[string]string{rgfilev1alpha1.RGInventoryIDLabel: inv.InventoryID}
299+
}
300+
if err := writeRGFile(p.UniquePath.String(), rg, filename); err != nil {
301+
return errors.E(op, p.UniquePath, err)
302+
}
303+
304+
// Rewrite Kptfile without inventory existing Kptfile contains inventory info. This
305+
// is required when a user appends the force flag.
306+
if !isEmpty {
307+
if err := kptfileutil.WriteFile(p.UniquePath.String(), kf); err != nil {
308+
return errors.E(op, p.UniquePath, err)
309+
}
310+
}
311+
312+
return nil
313+
}
314+
315+
// writeRGFile writes a ResourceGroup inventory to local disk.
316+
func writeRGFile(dir string, rg *rgfilev1alpha1.ResourceGroup, filename string) error {
317+
const op errors.Op = "cmdliveinit.writeRGFile"
318+
b, err := yaml.MarshalWithOptions(rg, &yaml.EncoderOptions{SeqIndent: yaml.WideSequenceStyle})
319+
if err != nil {
320+
return err
321+
}
322+
if _, err := os.Stat(filepath.Join(dir, filename)); err != nil && !goerrors.Is(err, os.ErrNotExist) {
323+
return errors.E(op, errors.IO, types.UniquePath(dir), err)
324+
}
325+
326+
// fyi: perm is ignored if the file already exists
327+
err = ioutil.WriteFile(filepath.Join(dir, filename), b, 0600)
328+
if err != nil {
329+
return errors.E(op, errors.IO, types.UniquePath(dir), err)
330+
}
331+
return nil
332+
}
333+
185334
// Run fills in the inventory object values into the Kptfile.
186335
func updateKptfile(p *pkg.Pkg, inv *kptfilev1.Inventory, force bool) error {
187336
const op errors.Op = "cmdliveinit.updateKptfile"

internal/cmdliveinit/cmdliveinit_test.go

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/GoogleContainerTools/kpt/internal/printer/fake"
1515
"github.com/GoogleContainerTools/kpt/internal/testutil"
1616
kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
17+
rgfilev1alpha1 "github.com/GoogleContainerTools/kpt/pkg/api/resourcegroup/v1alpha1"
1718
"github.com/stretchr/testify/assert"
1819
"k8s.io/cli-runtime/pkg/genericclioptions"
1920
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
@@ -58,6 +59,14 @@ inventory:
5859

5960
var testTime = time.Unix(5555555, 66666666)
6061

62+
var resourceGroupInventory = `
63+
apiVersion: kpt.dev/v1alpha1
64+
kind: ResourceGroup
65+
metadata:
66+
name: foo
67+
namespace: test-namespace
68+
`
69+
6170
func TestCmd_generateID(t *testing.T) {
6271
testCases := map[string]struct {
6372
namespace string
@@ -130,6 +139,8 @@ func TestCmd_Run_NoKptfile(t *testing.T) {
130139
func TestCmd_Run(t *testing.T) {
131140
testCases := map[string]struct {
132141
kptfile string
142+
resourcegroup string
143+
rgfilename string
133144
name string
134145
namespace string
135146
inventoryID string
@@ -161,15 +172,47 @@ func TestCmd_Run(t *testing.T) {
161172
InventoryID: "my-inv-id",
162173
},
163174
},
175+
"Provided values are used with custom resourcegroup filename": {
176+
kptfile: kptFile,
177+
rgfilename: "custom-rg.yaml",
178+
name: "my-pkg",
179+
namespace: "my-ns",
180+
inventoryID: "my-inv-id",
181+
expectedInventory: kptfilev1.Inventory{
182+
Namespace: "my-ns",
183+
Name: "my-pkg",
184+
InventoryID: "my-inv-id",
185+
},
186+
},
164187
"Kptfile with inventory already set is error": {
165188
kptfile: kptFileWithInventory,
166189
name: inventoryName,
167190
namespace: inventoryNamespace,
168191
inventoryID: inventoryID,
169192
force: false,
193+
expectedErrorMsg: "inventory information already set",
194+
},
195+
"ResourceGroup with inventory already set is error": {
196+
kptfile: kptFile,
197+
resourcegroup: resourceGroupInventory,
198+
rgfilename: "resourcegroup.yaml",
199+
name: inventoryName,
200+
namespace: inventoryNamespace,
201+
inventoryID: inventoryID,
202+
force: false,
170203
expectedErrorMsg: "inventory information already set for package",
171204
},
172-
"The force flag allows changing inventory information even if already set": {
205+
"ResourceGroup with inventory and Kptfile with inventory already set is error": {
206+
kptfile: kptFileWithInventory,
207+
resourcegroup: resourceGroupInventory,
208+
rgfilename: "resourcegroup.yaml",
209+
name: inventoryName,
210+
namespace: inventoryNamespace,
211+
inventoryID: inventoryID,
212+
force: false,
213+
expectedErrorMsg: "inventory information already set",
214+
},
215+
"The force flag allows changing inventory information even if already set in Kptfile": {
173216
kptfile: kptFileWithInventory,
174217
name: inventoryName,
175218
namespace: inventoryNamespace,
@@ -181,6 +224,20 @@ func TestCmd_Run(t *testing.T) {
181224
InventoryID: inventoryID,
182225
},
183226
},
227+
"The force flag allows changing inventory information even if already set in ResourceGroup": {
228+
kptfile: kptFile,
229+
resourcegroup: resourceGroupInventory,
230+
rgfilename: "resourcegroup.yaml",
231+
name: inventoryName,
232+
namespace: inventoryNamespace,
233+
inventoryID: inventoryID,
234+
force: true,
235+
expectedInventory: kptfilev1.Inventory{
236+
Namespace: inventoryNamespace,
237+
Name: inventoryName,
238+
InventoryID: inventoryID,
239+
},
240+
},
184241
}
185242

186243
for tn, tc := range testCases {
@@ -198,11 +255,21 @@ func TestCmd_Run(t *testing.T) {
198255
t.FailNow()
199256
}
200257

258+
// Create ResourceGroup file if testing the STDIN feature.
259+
if tc.resourcegroup != "" && tc.rgfilename != "" {
260+
err := ioutil.WriteFile(filepath.Join(w.WorkspaceDirectory, tc.rgfilename),
261+
[]byte(tc.resourcegroup), 0600)
262+
if !assert.NoError(t, err) {
263+
t.FailNow()
264+
}
265+
}
266+
201267
revert := testutil.Chdir(t, w.WorkspaceDirectory)
202268
defer revert()
203269

204270
runner := NewRunner(fake.CtxWithDefaultPrinter(), tf, ioStreams)
205271
runner.namespace = tc.namespace
272+
runner.RGFile = tc.rgfilename
206273
args := []string{
207274
"--name", tc.name,
208275
"--inventory-id", tc.inventoryID,
@@ -223,17 +290,39 @@ func TestCmd_Run(t *testing.T) {
223290
return
224291
}
225292

226-
// Otherwise, validate the kptfile values
293+
// Otherwise, validate the kptfile values and/or resourcegroup values.
294+
var actualInv kptfilev1.Inventory
227295
assert.NoError(t, err)
228296
kf, err := pkg.ReadKptfile(w.WorkspaceDirectory)
229297
assert.NoError(t, err)
230-
if !assert.NotNil(t, kf.Inventory) {
231-
t.FailNow()
298+
299+
switch tc.rgfilename {
300+
case "":
301+
if !assert.NotNil(t, kf.Inventory) {
302+
t.FailNow()
303+
}
304+
actualInv = *kf.Inventory
305+
default:
306+
// Check resourcegroup file if testing the STDIN feature.
307+
rg, err := pkg.ReadRGFile(w.WorkspaceDirectory, tc.rgfilename)
308+
assert.NoError(t, err)
309+
if !assert.NotNil(t, rg) {
310+
t.FailNow()
311+
}
312+
313+
// Convert resourcegroup inventory back to Kptfile structure so we can share assertion
314+
// logic for Kptfile inventory and ResourceGroup inventory structure.
315+
actualInv = kptfilev1.Inventory{
316+
Name: rg.Name,
317+
Namespace: rg.Namespace,
318+
InventoryID: rg.Labels[rgfilev1alpha1.RGInventoryIDLabel],
319+
}
232320
}
233-
actualInv := *kf.Inventory
321+
234322
expectedInv := tc.expectedInventory
235323
assertInventoryName(t, expectedInv.Name, actualInv.Name)
236324
assert.Equal(t, expectedInv.Namespace, actualInv.Namespace)
325+
237326
if tc.expectAutoGenID {
238327
assertGenInvID(t, actualInv.Name, actualInv.Namespace, actualInv.InventoryID)
239328
} else {

0 commit comments

Comments
 (0)