Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge main into porch #2750

Merged
merged 7 commits into from
Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions internal/cmdliveinit/cmdliveinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ package cmdliveinit
import (
"context"
"crypto/sha1"
goerrors "errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
Expand All @@ -16,16 +19,19 @@ import (
"github.com/GoogleContainerTools/kpt/internal/errors"
"github.com/GoogleContainerTools/kpt/internal/pkg"
"github.com/GoogleContainerTools/kpt/internal/printer"
"github.com/GoogleContainerTools/kpt/internal/types"
"github.com/GoogleContainerTools/kpt/internal/util/attribution"
"github.com/GoogleContainerTools/kpt/internal/util/pathutil"
kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
rgfilev1alpha1 "github.com/GoogleContainerTools/kpt/pkg/api/resourcegroup/v1alpha1"
"github.com/GoogleContainerTools/kpt/pkg/kptfile/kptfileutil"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
k8scmdutil "k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/config"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

const defaultInventoryName = "inventory"
Expand All @@ -38,6 +44,24 @@ func (i *InvExistsError) Error() string {
return "inventory information already set for package"
}

// InvInRGExistsError defines new error when the inventory
// values have already been set on the ResourceGroup file and we will warn
// the user to migrate rather than init. This is part of kpt live STDIN work.
type InvInRGExistsError struct{}

func (i *InvInRGExistsError) Error() string {
return "inventory information already set for package"
}

// InvInKfExistsError defines new error when the inventory
// values have already been set on the Kptfile and we will warn
// the user to migrate rather than init. This is part of kpt live STDIN work.
type InvInKfExistsError struct{}

func (i *InvInKfExistsError) Error() string {
return "inventory information already set within Kptfile for package"
}

func NewRunner(ctx context.Context, factory k8scmdutil.Factory,
ioStreams genericclioptions.IOStreams) *Runner {
r := &Runner{
Expand Down Expand Up @@ -76,6 +100,7 @@ type Runner struct {
Force bool // Set inventory values even if already set in Kptfile
Name string // Inventory object name
namespace string // Inventory object namespace
RGFile string // resourcegroup object filepath
InventoryID string // Inventory object unique identifier label
Quiet bool // Output message during initialization
}
Expand Down Expand Up @@ -112,6 +137,7 @@ func (r *Runner) runE(_ *cobra.Command, args []string) error {
Quiet: r.Quiet,
Name: r.Name,
InventoryID: r.InventoryID,
RGFileName: r.RGFile,
Force: r.Force,
}).Run(r.ctx)
if err != nil {
Expand All @@ -129,12 +155,22 @@ type ConfigureInventoryInfo struct {

Name string
InventoryID string
RGFileName string

Force bool
}

// Run updates the inventory info in the package given by the Path.
func (c *ConfigureInventoryInfo) Run(ctx context.Context) error {
// Use ResourceGroup file for inventory logic if the resourcegroup file
// is set directly. For this feature gate, the resourcegroup must be directly set
// through our tests since we are not exposing this through the command surface as a
// flag, currently. When we promote this, the resourcegroup filename can be empty and
// the default filename value will be inferred/used.
if c.RGFileName != "" {
return c.runLiveInitWithRGFile(ctx)
}

const op errors.Op = "cmdliveinit.Run"
pr := printer.FromContextOrDie(ctx)

Expand Down Expand Up @@ -189,6 +225,119 @@ func (c *ConfigureInventoryInfo) Run(ctx context.Context) error {
return nil
}

// func runLiveInitWithRGFile is a modified version of ConfigureInventoryInfo.Run that stores the
// package inventory information in a separate resourcegroup file. The logic for this is branched into
// a separate function to enable feature gating.
func (c *ConfigureInventoryInfo) runLiveInitWithRGFile(ctx context.Context) error {
const op errors.Op = "cmdliveinit.runLiveInitWithRGFile"
pr := printer.FromContextOrDie(ctx)

namespace, err := config.FindNamespace(c.Factory.ToRawKubeConfigLoader(), c.Pkg.UniquePath.String())
if err != nil {
return errors.E(op, c.Pkg.UniquePath, err)
}
namespace = strings.TrimSpace(namespace)
if !c.Quiet {
pr.Printf("initializing ResourceGroup inventory info (namespace: %s)...", namespace)
}

// Autogenerate the name if it is not provided through the flag.
if c.Name == "" {
randomSuffix := common.RandomStr()
c.Name = fmt.Sprintf("%s-%s", defaultInventoryName, randomSuffix)
}

// Finally, create a ResourceGroup containing the inventory information.
err = createRGFile(c.Pkg, &kptfilev1.Inventory{
Namespace: namespace,
Name: c.Name,
InventoryID: c.InventoryID,
}, c.RGFileName, c.Force)
if !c.Quiet {
if err == nil {
pr.Printf("success\n")
} else {
pr.Printf("failed\n")
}
}
if err != nil {
return errors.E(op, c.Pkg.UniquePath, err)
}
// add metrics annotation to package resources to track the usage as the resources
// will be applied using kpt live group
at := attribution.Attributor{PackagePaths: []string{c.Pkg.UniquePath.String()}, CmdGroup: "live"}
at.Process()
return nil
}

// createRGFile fills in the inventory object values into the resourcegroup object and writes to file storage.
func createRGFile(p *pkg.Pkg, inv *kptfilev1.Inventory, filename string, force bool) error {
const op errors.Op = "cmdliveinit.createRGFile"
// Read the resourcegroup object io io.dir
rg, err := p.ReadRGFile(filename)
if err != nil && !goerrors.Is(err, os.ErrNotExist) {
return errors.E(op, p.UniquePath, err)
}

// Read the Kptfile to ensure that inventory information is not in Kptfile either.
kf, err := p.Kptfile()
if err != nil {
return errors.E(op, p.UniquePath, err)
}
// Validate the inventory values don't exist in Kptfile.
isEmpty := kptfileInventoryEmpty(kf.Inventory)
if !isEmpty && !force {
return errors.E(op, p.UniquePath, &InvInKfExistsError{})
}
// Set the Kptfile inventory to be nil if we force write to resourcegroup instead.
kf.Inventory = nil

// Validate the inventory values don't already exist in Resourcegroup.
if rg != nil && !force {
return errors.E(op, p.UniquePath, &InvExistsError{})
}
// Initialize new resourcegroup object, as rg should have been nil.
rg = &rgfilev1alpha1.ResourceGroup{ResourceMeta: rgfilev1alpha1.DefaultMeta}
// // Finally, set the inventory parameters in the ResourceGroup object and write it.
rg.Name = inv.Name
rg.Namespace = inv.Namespace
if inv.InventoryID != "" {
rg.Labels = map[string]string{rgfilev1alpha1.RGInventoryIDLabel: inv.InventoryID}
}
if err := writeRGFile(p.UniquePath.String(), rg, filename); err != nil {
return errors.E(op, p.UniquePath, err)
}

// Rewrite Kptfile without inventory existing Kptfile contains inventory info. This
// is required when a user appends the force flag.
if !isEmpty {
if err := kptfileutil.WriteFile(p.UniquePath.String(), kf); err != nil {
return errors.E(op, p.UniquePath, err)
}
}

return nil
}

// writeRGFile writes a ResourceGroup inventory to local disk.
func writeRGFile(dir string, rg *rgfilev1alpha1.ResourceGroup, filename string) error {
const op errors.Op = "cmdliveinit.writeRGFile"
b, err := yaml.MarshalWithOptions(rg, &yaml.EncoderOptions{SeqIndent: yaml.WideSequenceStyle})
if err != nil {
return err
}
if _, err := os.Stat(filepath.Join(dir, filename)); err != nil && !goerrors.Is(err, os.ErrNotExist) {
return errors.E(op, errors.IO, types.UniquePath(dir), err)
}

// fyi: perm is ignored if the file already exists
err = ioutil.WriteFile(filepath.Join(dir, filename), b, 0600)
if err != nil {
return errors.E(op, errors.IO, types.UniquePath(dir), err)
}
return nil
}

// Run fills in the inventory object values into the Kptfile.
func updateKptfile(p *pkg.Pkg, inv *kptfilev1.Inventory, force bool) error {
const op errors.Op = "cmdliveinit.updateKptfile"
Expand Down
99 changes: 94 additions & 5 deletions internal/cmdliveinit/cmdliveinit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/GoogleContainerTools/kpt/internal/printer/fake"
"github.com/GoogleContainerTools/kpt/internal/testutil"
kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
rgfilev1alpha1 "github.com/GoogleContainerTools/kpt/pkg/api/resourcegroup/v1alpha1"
"github.com/stretchr/testify/assert"
"k8s.io/cli-runtime/pkg/genericclioptions"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
Expand Down Expand Up @@ -59,6 +60,14 @@ inventory:

var testTime = time.Unix(5555555, 66666666)

var resourceGroupInventory = `
apiVersion: kpt.dev/v1alpha1
kind: ResourceGroup
metadata:
name: foo
namespace: test-namespace
`

func TestCmd_generateID(t *testing.T) {
testCases := map[string]struct {
namespace string
Expand Down Expand Up @@ -131,6 +140,8 @@ func TestCmd_Run_NoKptfile(t *testing.T) {
func TestCmd_Run(t *testing.T) {
testCases := map[string]struct {
kptfile string
resourcegroup string
rgfilename string
name string
namespace string
inventoryID string
Expand Down Expand Up @@ -162,15 +173,47 @@ func TestCmd_Run(t *testing.T) {
InventoryID: "my-inv-id",
},
},
"Provided values are used with custom resourcegroup filename": {
kptfile: kptFile,
rgfilename: "custom-rg.yaml",
name: "my-pkg",
namespace: "my-ns",
inventoryID: "my-inv-id",
expectedInventory: kptfilev1.Inventory{
Namespace: "my-ns",
Name: "my-pkg",
InventoryID: "my-inv-id",
},
},
"Kptfile with inventory already set is error": {
kptfile: kptFileWithInventory,
name: inventoryName,
namespace: inventoryNamespace,
inventoryID: inventoryID,
force: false,
expectedErrorMsg: "inventory information already set",
},
"ResourceGroup with inventory already set is error": {
kptfile: kptFile,
resourcegroup: resourceGroupInventory,
rgfilename: "resourcegroup.yaml",
name: inventoryName,
namespace: inventoryNamespace,
inventoryID: inventoryID,
force: false,
expectedErrorMsg: "inventory information already set for package",
},
"The force flag allows changing inventory information even if already set": {
"ResourceGroup with inventory and Kptfile with inventory already set is error": {
kptfile: kptFileWithInventory,
resourcegroup: resourceGroupInventory,
rgfilename: "resourcegroup.yaml",
name: inventoryName,
namespace: inventoryNamespace,
inventoryID: inventoryID,
force: false,
expectedErrorMsg: "inventory information already set",
},
"The force flag allows changing inventory information even if already set in Kptfile": {
kptfile: kptFileWithInventory,
name: inventoryName,
namespace: inventoryNamespace,
Expand All @@ -182,6 +225,20 @@ func TestCmd_Run(t *testing.T) {
InventoryID: inventoryID,
},
},
"The force flag allows changing inventory information even if already set in ResourceGroup": {
kptfile: kptFile,
resourcegroup: resourceGroupInventory,
rgfilename: "resourcegroup.yaml",
name: inventoryName,
namespace: inventoryNamespace,
inventoryID: inventoryID,
force: true,
expectedInventory: kptfilev1.Inventory{
Namespace: inventoryNamespace,
Name: inventoryName,
InventoryID: inventoryID,
},
},
}

for tn, tc := range testCases {
Expand All @@ -199,11 +256,21 @@ func TestCmd_Run(t *testing.T) {
t.FailNow()
}

// Create ResourceGroup file if testing the STDIN feature.
if tc.resourcegroup != "" && tc.rgfilename != "" {
err := ioutil.WriteFile(filepath.Join(w.WorkspaceDirectory, tc.rgfilename),
[]byte(tc.resourcegroup), 0600)
if !assert.NoError(t, err) {
t.FailNow()
}
}

revert := testutil.Chdir(t, w.WorkspaceDirectory)
defer revert()

runner := NewRunner(fake.CtxWithDefaultPrinter(), tf, ioStreams)
runner.namespace = tc.namespace
runner.RGFile = tc.rgfilename
args := []string{
"--name", tc.name,
"--inventory-id", tc.inventoryID,
Expand All @@ -224,17 +291,39 @@ func TestCmd_Run(t *testing.T) {
return
}

// Otherwise, validate the kptfile values
// Otherwise, validate the kptfile values and/or resourcegroup values.
var actualInv kptfilev1.Inventory
assert.NoError(t, err)
kf, err := pkg.ReadKptfile(filesys.FileSystemOrOnDisk{}, w.WorkspaceDirectory)
assert.NoError(t, err)
if !assert.NotNil(t, kf.Inventory) {
t.FailNow()

switch tc.rgfilename {
case "":
if !assert.NotNil(t, kf.Inventory) {
t.FailNow()
}
actualInv = *kf.Inventory
default:
// Check resourcegroup file if testing the STDIN feature.
rg, err := pkg.ReadRGFile(w.WorkspaceDirectory, tc.rgfilename)
assert.NoError(t, err)
if !assert.NotNil(t, rg) {
t.FailNow()
}

// Convert resourcegroup inventory back to Kptfile structure so we can share assertion
// logic for Kptfile inventory and ResourceGroup inventory structure.
actualInv = kptfilev1.Inventory{
Name: rg.Name,
Namespace: rg.Namespace,
InventoryID: rg.Labels[rgfilev1alpha1.RGInventoryIDLabel],
}
}
actualInv := *kf.Inventory

expectedInv := tc.expectedInventory
assertInventoryName(t, expectedInv.Name, actualInv.Name)
assert.Equal(t, expectedInv.Namespace, actualInv.Namespace)

if tc.expectAutoGenID {
assertGenInvID(t, actualInv.Name, actualInv.Namespace, actualInv.InventoryID)
} else {
Expand Down
Loading