Skip to content
This repository was archived by the owner on Jun 22, 2023. It is now read-only.

Commit f4bbb00

Browse files
committed
Add CLI command for binding a catalog entry to a workspace
The kcp-catalog bind command will bind a catalog entry to a workspace by creating a singular or multiple APIBinding using ExportReference listed in spec.References in the catalog entry. Signed-off-by: Vu Dinh <vudinh@outlook.com>
1 parent 773945d commit f4bbb00

File tree

4 files changed

+247
-14
lines changed

4 files changed

+247
-14
lines changed

cmd/kcp-catalog/bind/bind.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
Copyright 2021 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package bind
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
"github.com/spf13/cobra"
24+
25+
apisv1alpha1 "github.com/kcp-dev/kcp/pkg/apis/apis/v1alpha1"
26+
kcpclient "github.com/kcp-dev/kcp/pkg/client/clientset/versioned"
27+
"github.com/kcp-dev/kcp/pkg/cmd/help"
28+
"k8s.io/client-go/tools/clientcmd"
29+
"k8s.io/klog/v2"
30+
)
31+
32+
func NewCmd() {
33+
bindCmd := &cobra.Command{
34+
Use: "bind",
35+
Short: "Bind a catalog entry to a workspace",
36+
Long: help.Doc(`
37+
Bind a catalog entry to a workspace
38+
39+
The bind process creates an APIBinding using the ExportReference information
40+
in the CatalogEntry. The RBAC that is required to use APIBinding is not created
41+
as a part of this process and must be handled separately.
42+
`),
43+
RunE: runBindCmdFunc,
44+
}
45+
46+
// TODO(dinhxuanvu): Use flags to accept inputs for now. Need to reevaluate this
47+
// approach later to see if this is a good UX
48+
bindCmd.Flags().StringP("entryname", "-e", "", "the name of the catalog entry")
49+
if err := bindCmd.MarkFlagRequired("entryname"); err != nil {
50+
klog.Fatalf("Failed to set required `entryname` flag for `bind` command: %s", err.Error())
51+
}
52+
bindCmd.Flags().StringP("workspace", "-w", "", "the workspace path where catalog entry locates")
53+
if err := bindCmd.MarkFlagRequired("workspace"); err != nil {
54+
klog.Fatalf("Failed to set required `workspace` flag for `bind` command: %s", err.Error())
55+
}
56+
bindCmd.Flags().StringP("target", "-t", "", "the targeted workspace where the entry should be binded to")
57+
if err := bindCmd.MarkFlagRequired("target"); err != nil {
58+
klog.Fatalf("Failed to set required `target` flag for `bind` command: %s", err.Error())
59+
}
60+
61+
return bindCmd
62+
}
63+
64+
// runBindCmdFunc creates APIBindings from the list of References in CatalogEntry
65+
// User is responsible to create all of permissons/RBAC necessary to enable
66+
// APIBindings to work in the particular workspace.
67+
// TODO(dinhxuanvu): construct a permission/RBAC request model
68+
func runBindCmdFunc(cmd *cobra.Command, _ []string) error {
69+
// Get kubeconfig to access the cluster
70+
// Assume this cluster has kcp all setup and running
71+
kubeconfigPath := cmd.Flag("kubeconfig").Value.String()
72+
configLoader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath}, nil)
73+
config, err := configLoader.ClientConfig()
74+
if err != nil {
75+
return err
76+
}
77+
78+
kcpClient, err := kcpclient.NewClusterForConfig(config)
79+
if err != nil {
80+
return fmt.Errorf("failed to create kcp client: %w", err)
81+
}
82+
83+
entryName := cmd.Flag("entryname").Value.String()
84+
workspace := cmd.Flag("workspace").Value.String()
85+
targetWS := cmd.Flag("target").Value.String()
86+
// Get the catalog entry from the worksplace using RESTClient for now
87+
// TODO(dinhxuanvu): potentially want to add catalog entry into typed clientset
88+
// or create its own clienset in the repo
89+
// TODO(dinhxuanvu): figure out the AbsPath for workspace and api info
90+
entry, err := kcpClient.RESTClient().
91+
Get().
92+
AbsPath("/apis/<api>/<version>").
93+
Resource("catalogentries").
94+
Name(entryName).
95+
DoRaw(context.TODO())
96+
if err != nil {
97+
return fmt.Errorf("failed to retrieve catalog entry: %w", err)
98+
}
99+
100+
// Ensure that the target workspace exists
101+
wp, err := kcpClient.TenancyV1beta1().Workspaces().Get(context.TODO(), targetWS, metav1.CreateOptions{})
102+
if err != nil {
103+
return fmt.Errorf("failed to retrieve target workspace: %w", err)
104+
}
105+
106+
// Generate the APIBinding for each reference in catalog entry
107+
apiBindings := []apisv1alpha1.APIBinding{}
108+
for _, ref := range entry.References {
109+
// Check if ref is valid. Skip if invalid
110+
if ref.Workspace.Path == "" || ref.Workspace.ExportName == "" {
111+
klog.Infof("Invalid reference: %q/%q", ref.Workspace.Path, ref.Workspace.ExportName)
112+
continue
113+
}
114+
apibinding := &apisv1alpha1.APIBinding{
115+
ObjectMeta: metav1.ObjectMeta{
116+
Name: ref.Workspace.ExportName,
117+
},
118+
Spec: apisv1alpha1.APIBindingSpec{
119+
Reference: ref,
120+
},
121+
}
122+
apiBindings = append(apiBindings, apibinding)
123+
}
124+
125+
// Apply the APIBinding(s) to the target workspace
126+
for _, binding := range apiBindings {
127+
created, err := kcpClient.ApisV1alpha1().APIBindings().Create(context.TODO(), binding, metav1.CreateOptions{})
128+
if err != nil {
129+
klog.Infof("Failed to create API binding %s: %w", binding.Name, err)
130+
continue
131+
}
132+
133+
if apierrors.IsAlreadyExists(err) {
134+
klog.Infof("APIBinding %s already exists in workspace %s", binding.Name, targetWS)
135+
}
136+
137+
existing, err := kcpClient.ApisV1alpha1().APIBindings().Get(context.TODO(), binding.Name, metav1.GetOptions{})
138+
if err != nil {
139+
return err
140+
}
141+
142+
klog.Infof("Updating API binding %s", binding.Name)
143+
existing.Spec = binding.Spec
144+
if _, err := kcpClient.ApisV1alpha1().APIBindings().Update(ctx, existing, metav1.UpdateOptions{}); err != nil {
145+
return fmt.Errorf("could not update API binding %s in workspace %s: %w", existing.Name, targetWS, err)
146+
}
147+
}
148+
}

cmd/kcp-catalog/main.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2021 The KCP Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"os"
21+
22+
"github.com/spf13/cobra"
23+
24+
"k8s.io/component-base/cli"
25+
"k8s.io/component-base/version"
26+
27+
"github.com/kcp-dev/kcp/pkg/cmd/help"
28+
)
29+
30+
func main() {
31+
cmd := &cobra.Command{
32+
Use: "kcp-catalog",
33+
Short: "KCP Catalog",
34+
Long: help.Doc(`
35+
kcp-catalog is a CLI tool to manage Catalog API objects.
36+
`),
37+
}
38+
39+
// TODO(dinhxuanvu): Use kubeconfig flag to get access to the kcp cluster.
40+
// Later, potentially expand to other options such as KUBECONFIG env or .kcp
41+
// directory
42+
cmd.PersistentFlags().String("kubeconfig", ".kubeconfig", "kubeconfig file used to contact the cluster.")
43+
cmd.AddCommand(bind.NewCmd())
44+
45+
help.FitTerminal(cmd.OutOrStdout())
46+
47+
if v := version.Get().String(); len(v) == 0 {
48+
cmd.Version = "<unknown>"
49+
} else {
50+
cmd.Version = v
51+
}
52+
53+
os.Exit(cli.Run(cmd))
54+
}

go.mod

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,38 @@ module github.com/kcp-dev/catalog
33
go 1.18
44

55
require (
6+
github.com/kcp-dev/kcp v0.8.2
67
github.com/kcp-dev/kcp/pkg/apis v0.7.8
78
github.com/onsi/ginkgo v1.16.5
89
github.com/onsi/gomega v1.17.0
10+
github.com/spf13/cobra v1.4.0
911
k8s.io/apimachinery v0.24.3
1012
k8s.io/client-go v0.24.3
13+
k8s.io/component-base v0.24.3
14+
k8s.io/klog/v2 v2.60.1
1115
sigs.k8s.io/controller-runtime v0.11.2
1216
)
1317

1418
require (
1519
cloud.google.com/go v0.81.0 // indirect
20+
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
1621
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
1722
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
1823
github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
1924
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
2025
github.com/Azure/go-autorest/logger v0.2.1 // indirect
2126
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
27+
github.com/MakeNowJust/heredoc v1.0.0 // indirect
2228
github.com/PuerkitoBio/purell v1.1.1 // indirect
2329
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
2430
github.com/beorn7/perks v1.0.1 // indirect
2531
github.com/cespare/xxhash/v2 v2.1.2 // indirect
2632
github.com/davecgh/go-spew v1.1.1 // indirect
2733
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
28-
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
34+
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
2935
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
3036
github.com/fsnotify/fsnotify v1.5.1 // indirect
31-
github.com/go-logr/logr v1.2.0 // indirect
37+
github.com/go-logr/logr v1.2.3 // indirect
3238
github.com/go-logr/zapr v1.2.0 // indirect
3339
github.com/go-openapi/jsonpointer v0.19.5 // indirect
3440
github.com/go-openapi/jsonreference v0.19.5 // indirect
@@ -37,16 +43,21 @@ require (
3743
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
3844
github.com/golang/protobuf v1.5.2 // indirect
3945
github.com/google/gnostic v0.5.7-v3refs // indirect
40-
github.com/google/go-cmp v0.5.5 // indirect
46+
github.com/google/go-cmp v0.5.6 // indirect
4147
github.com/google/gofuzz v1.1.0 // indirect
4248
github.com/google/uuid v1.1.2 // indirect
4349
github.com/imdario/mergo v0.3.12 // indirect
50+
github.com/inconshreveable/mousetrap v1.0.0 // indirect
4451
github.com/josharian/intern v1.0.0 // indirect
4552
github.com/json-iterator/go v1.1.12 // indirect
53+
github.com/kcp-dev/logicalcluster/v2 v2.0.0-alpha.1 // indirect
4654
github.com/mailru/easyjson v0.7.6 // indirect
55+
github.com/mattn/go-runewidth v0.0.7 // indirect
4756
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
57+
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
4858
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
4959
github.com/modern-go/reflect2 v1.0.2 // indirect
60+
github.com/muesli/reflow v0.1.0 // indirect
5061
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
5162
github.com/nxadm/tail v1.4.8 // indirect
5263
github.com/pkg/errors v0.9.1 // indirect
@@ -56,12 +67,12 @@ require (
5667
github.com/prometheus/procfs v0.7.3 // indirect
5768
github.com/spf13/pflag v1.0.5 // indirect
5869
go.uber.org/atomic v1.7.0 // indirect
59-
go.uber.org/multierr v1.6.0 // indirect
70+
go.uber.org/multierr v1.7.0 // indirect
6071
go.uber.org/zap v1.19.1 // indirect
6172
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
6273
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
6374
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
64-
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
75+
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
6576
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
6677
golang.org/x/text v0.3.7 // indirect
6778
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
@@ -74,11 +85,9 @@ require (
7485
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
7586
k8s.io/api v0.24.3 // indirect
7687
k8s.io/apiextensions-apiserver v0.24.3 // indirect
77-
k8s.io/component-base v0.24.3 // indirect
78-
k8s.io/klog/v2 v2.60.1 // indirect
7988
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
8089
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
8190
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
82-
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
91+
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
8392
sigs.k8s.io/yaml v1.3.0 // indirect
8493
)

0 commit comments

Comments
 (0)