Skip to content

Commit 1c9af4e

Browse files
authored
Refactor install RG command (#2803)
1 parent 711387e commit 1c9af4e

File tree

7 files changed

+203
-146
lines changed

7 files changed

+203
-146
lines changed

commands/installrg.go

Lines changed: 0 additions & 65 deletions
This file was deleted.

commands/livecmd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/GoogleContainerTools/kpt/internal/cmdapply"
2424
"github.com/GoogleContainerTools/kpt/internal/cmddestroy"
25+
"github.com/GoogleContainerTools/kpt/internal/cmdinstallrg"
2526
"github.com/GoogleContainerTools/kpt/internal/cmdliveinit"
2627
"github.com/GoogleContainerTools/kpt/internal/cmdmigrate"
2728
"github.com/GoogleContainerTools/kpt/internal/docs/generated/livedocs"
@@ -56,7 +57,7 @@ func GetLiveCommand(ctx context.Context, _, version string) *cobra.Command {
5657
applyCmd := cmdapply.NewCommand(ctx, f, ioStreams)
5758
destroyCmd := cmddestroy.NewCommand(ctx, f, ioStreams)
5859
statusCmd := status.NewCommand(ctx, f)
59-
installRGCmd := GetInstallRGRunner(f, ioStreams).Command
60+
installRGCmd := cmdinstallrg.NewCommand(ctx, f, ioStreams)
6061
liveCmd.AddCommand(initCmd, applyCmd, destroyCmd, statusCmd, installRGCmd)
6162

6263
// Add the migrate command to change from ConfigMap to ResourceGroup inventory

internal/cmdinstallrg/cmdinstallrg.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2022 Google LLC
2+
//
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+
package cmdinstallrg
16+
17+
import (
18+
"context"
19+
"fmt"
20+
21+
"github.com/GoogleContainerTools/kpt/internal/docs/generated/livedocs"
22+
"github.com/GoogleContainerTools/kpt/pkg/live"
23+
"github.com/spf13/cobra"
24+
"k8s.io/cli-runtime/pkg/genericclioptions"
25+
cmdutil "k8s.io/kubectl/pkg/cmd/util"
26+
)
27+
28+
// NewRunner returns a command runner
29+
func NewRunner(ctx context.Context, factory cmdutil.Factory,
30+
ioStreams genericclioptions.IOStreams) *Runner {
31+
32+
r := &Runner{
33+
ctx: ctx,
34+
ioStreams: ioStreams,
35+
factory: factory,
36+
}
37+
c := &cobra.Command{
38+
Use: "install-resource-group",
39+
RunE: r.runE,
40+
PreRunE: r.preRunE,
41+
Short: livedocs.InstallResourceGroupShort,
42+
Long: livedocs.InstallResourceGroupShort + "\n" + livedocs.InstallResourceGroupLong,
43+
Example: livedocs.InstallResourceGroupExamples,
44+
}
45+
r.Command = c
46+
return r
47+
}
48+
49+
func NewCommand(ctx context.Context, factory cmdutil.Factory,
50+
ioStreams genericclioptions.IOStreams) *cobra.Command {
51+
return NewRunner(ctx, factory, ioStreams).Command
52+
}
53+
54+
// Runner contains the run function
55+
type Runner struct {
56+
ctx context.Context
57+
Command *cobra.Command
58+
ioStreams genericclioptions.IOStreams
59+
factory cmdutil.Factory
60+
}
61+
62+
func (r *Runner) preRunE(_ *cobra.Command, _ []string) error {
63+
return nil
64+
}
65+
66+
func (r *Runner) runE(_ *cobra.Command, args []string) error {
67+
// Validate the number of arguments.
68+
if len(args) > 0 {
69+
return fmt.Errorf("too many arguments; install-resource-group takes no arguments")
70+
}
71+
fmt.Fprint(r.ioStreams.Out, "installing inventory ResourceGroup CRD...")
72+
73+
err := (&live.ResourceGroupInstaller{
74+
Factory: r.factory,
75+
}).InstallRG(r.ctx)
76+
77+
if err == nil {
78+
fmt.Fprintln(r.ioStreams.Out, "success")
79+
} else {
80+
fmt.Fprintln(r.ioStreams.Out, "failed")
81+
}
82+
return err
83+
}

internal/cmdmigrate/migratecmd.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,10 @@ func (mr *MigrateRunner) applyCRD() error {
196196
return nil
197197
}
198198
// Install the ResourceGroup CRD to the cluster.
199-
err := live.InstallResourceGroupCRD(mr.factory)
199+
200+
err := (&live.ResourceGroupInstaller{
201+
Factory: mr.factory,
202+
}).InstallRG(mr.ctx)
200203
if err == nil {
201204
fmt.Fprintln(mr.ioStreams.Out, "success")
202205
} else {

internal/cmdutil/util.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ import (
3434
func InstallResourceGroupCRD(ctx context.Context, f util.Factory) error {
3535
pr := printer.FromContextOrDie(ctx)
3636
pr.Printf("installing inventory ResourceGroup CRD.\n")
37-
err := live.InstallResourceGroupCRD(f)
37+
err := (&live.ResourceGroupInstaller{
38+
Factory: f,
39+
}).InstallRG(ctx)
3840
if err != nil {
3941
return &ResourceGroupCRDInstallError{
4042
Err: err,
@@ -78,12 +80,12 @@ func (*NoResourceGroupCRDError) Error() string {
7880
// ResourceGroupCRDNotLatestError is an error type that will be used when a
7981
// cluster has a ResourceGroup CRD that doesn't match the
8082
// latest declaration.
81-
type ResourceGroupCRDNotLatestError struct{
83+
type ResourceGroupCRDNotLatestError struct {
8284
Err error
8385
}
8486

8587
func (e *ResourceGroupCRDNotLatestError) Error() string {
8688
return fmt.Sprintf(
87-
"Type ResourceGroup CRD needs update. Please make sure you have the permission " +
88-
"to update CRD then run `kpt live install-resource-group`.\n %v", e.Err)
89+
"Type ResourceGroup CRD needs update. Please make sure you have the permission "+
90+
"to update CRD then run `kpt live install-resource-group`.\n %v", e.Err)
8991
}

pkg/live/inventoryrg.go

Lines changed: 79 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,29 @@ import (
2222
"time"
2323

2424
"github.com/GoogleContainerTools/kpt/pkg/status"
25+
"github.com/GoogleContainerTools/kpt/thirdparty/cli-utils/pkg/apply/taskrunner"
2526
apierrors "k8s.io/apimachinery/pkg/api/errors"
2627
"k8s.io/apimachinery/pkg/api/meta"
2728
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2829
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30+
"k8s.io/apimachinery/pkg/runtime"
2931
"k8s.io/apimachinery/pkg/runtime/schema"
32+
"k8s.io/cli-runtime/pkg/resource"
3033
"k8s.io/klog/v2"
3134
cmdutil "k8s.io/kubectl/pkg/cmd/util"
32-
"sigs.k8s.io/cli-utils/pkg/apply/cache"
33-
"sigs.k8s.io/cli-utils/pkg/apply/event"
34-
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
35+
"k8s.io/kubectl/pkg/util"
3536
"sigs.k8s.io/cli-utils/pkg/common"
3637
"sigs.k8s.io/cli-utils/pkg/inventory"
38+
"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
39+
pollevent "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
40+
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
3741
"sigs.k8s.io/cli-utils/pkg/object"
3842
"sigs.k8s.io/kustomize/kyaml/yaml"
3943
)
4044

4145
const (
42-
applyCRDTimeout = 10 * time.Second
43-
applyCRDPollInterval = 2 * time.Second
46+
applyRGTimeout = 10 * time.Second
47+
applyRGPollInterval = 2 * time.Second
4448
)
4549

4650
// ResourceGroupGVK is the group/version/kind of the custom
@@ -296,83 +300,83 @@ func ResourceGroupCRDMatched(factory cmdutil.Factory) bool {
296300
return reflect.DeepEqual(liveSpec, latestspec)
297301
}
298302

299-
// InstallResourceGroupCRD applies the custom resource definition for the
300-
// ResourceGroup by creating and running a TaskQueue of Tasks necessary.
301-
// The Tasks are 1) Apply CRD task, 2) Wait Task (for CRD to become
302-
// established), and 3) Reset RESTMapper task. Returns an error if
303-
// a non-"AlreadyExists" error is returned on the event channel.
304-
// Runs the CRD installation in a separate goroutine (timeout
305-
// ensures no hanging).
306-
func InstallResourceGroupCRD(factory cmdutil.Factory) error {
307-
eventChannel := make(chan event.Event)
308-
go func() {
309-
defer close(eventChannel)
310-
mapper, err := factory.ToRESTMapper()
311-
if err != nil {
312-
handleError(eventChannel, err)
313-
return
314-
}
315-
crd, err := rgCRD(mapper)
316-
if err != nil {
317-
handleError(eventChannel, err)
318-
return
319-
}
320-
// Create the task to apply the ResourceGroup CRD.
321-
applyRGTask := NewApplyCRDTask(factory, crd)
322-
objs := object.UnstructuredSetToObjMetadataSet([]*unstructured.Unstructured{crd})
323-
// Create the tasks to apply the ResourceGroup CRD.
324-
tasks := []taskrunner.Task{
325-
applyRGTask,
326-
taskrunner.NewWaitTask("wait-rg-crd", objs, taskrunner.AllCurrent,
327-
applyCRDTimeout, mapper),
328-
}
329-
// Create the task queue channel, and send tasks in order into the channel.
330-
taskQueue := make(chan taskrunner.Task, len(tasks))
331-
for _, t := range tasks {
332-
taskQueue <- t
333-
}
334-
statusPoller, err := status.NewStatusPoller(factory)
335-
if err != nil {
336-
handleError(eventChannel, err)
337-
return
338-
}
339-
// Create a new cache map to hold the last known resource state & status
340-
resourceCache := cache.NewResourceCacheMap()
341-
// Run the task queue.
342-
runner := taskrunner.NewTaskStatusRunner(objs, statusPoller, resourceCache)
343-
err = runner.Run(context.Background(), taskQueue, eventChannel, taskrunner.Options{
344-
PollInterval: applyCRDPollInterval,
345-
UseCache: true,
346-
EmitStatusEvents: true,
347-
})
348-
if err != nil {
349-
handleError(eventChannel, err)
350-
return
351-
}
352-
}()
303+
// ResourceGroupInstaller can install the ResourceGroup CRD into a cluster.
304+
type ResourceGroupInstaller struct {
305+
Factory cmdutil.Factory
306+
}
353307

354-
// Return the error on the eventChannel if it exists; return
355-
// closes the channel. "AlreadyExists" is NOT an error.
356-
for e := range eventChannel {
357-
if e.Type == event.ErrorType {
358-
err := e.ErrorEvent.Err
359-
if !apierrors.IsAlreadyExists(err) {
360-
return err
361-
}
308+
func (rgi *ResourceGroupInstaller) InstallRG(ctx context.Context) error {
309+
poller, err := status.NewStatusPoller(rgi.Factory)
310+
if err != nil {
311+
return err
312+
}
313+
314+
mapper, err := rgi.Factory.ToRESTMapper()
315+
if err != nil {
316+
return err
317+
}
318+
319+
crd, err := rgCRD(mapper)
320+
if err != nil {
321+
return err
322+
}
323+
324+
if err := rgi.applyRG(crd); err != nil {
325+
if apierrors.IsAlreadyExists(err) {
326+
return nil
362327
}
328+
return err
363329
}
364330

365-
return nil
331+
objs := object.UnstructuredSetToObjMetadataSet([]*unstructured.Unstructured{crd})
332+
ctx, cancel := context.WithTimeout(ctx, applyRGTimeout)
333+
return func() error {
334+
defer cancel()
335+
for e := range poller.Poll(ctx, objs, polling.Options{PollInterval: applyRGPollInterval}) {
336+
switch e.EventType {
337+
case pollevent.ErrorEvent:
338+
return e.Error
339+
case pollevent.ResourceUpdateEvent:
340+
if e.Resource.Status == kstatus.CurrentStatus {
341+
// TODO: Replace this with a call to meta.MaybeResetRESTMapper
342+
// once we update the k8s libraries.
343+
m, err := taskrunner.ExtractDeferredDiscoveryRESTMapper(mapper)
344+
if err != nil {
345+
return err
346+
}
347+
m.Reset()
348+
return nil
349+
}
350+
}
351+
}
352+
return nil
353+
}()
366354
}
367355

368-
// handleError sends an error onto the event channel.
369-
func handleError(eventChannel chan event.Event, err error) {
370-
eventChannel <- event.Event{
371-
Type: event.ErrorType,
372-
ErrorEvent: event.ErrorEvent{
373-
Err: err,
374-
},
356+
func (rgi *ResourceGroupInstaller) applyRG(crd runtime.Object) error {
357+
mapper, err := rgi.Factory.ToRESTMapper()
358+
if err != nil {
359+
return err
360+
}
361+
mapping, err := mapper.RESTMapping(crdGroupKind)
362+
if err != nil {
363+
return err
364+
}
365+
client, err := rgi.Factory.UnstructuredClientForMapping(mapping)
366+
if err != nil {
367+
return err
368+
}
369+
370+
// Set the "last-applied-annotation" so future applies work correctly.
371+
if err := util.CreateApplyAnnotation(crd, unstructured.UnstructuredJSONScheme); err != nil {
372+
return err
375373
}
374+
// Apply the CRD to the cluster and ignore already exists error.
375+
var clearResourceVersion = false
376+
var emptyNamespace = ""
377+
helper := resource.NewHelper(client, mapping)
378+
_, err = helper.Create(emptyNamespace, clearResourceVersion, crd)
379+
return err
376380
}
377381

378382
// rgCRD returns the ResourceGroup CRD in Unstructured format or an error.

0 commit comments

Comments
 (0)