Skip to content

Commit a8e0815

Browse files
authored
Merge pull request #40 from ecordell/rmcr
Remove dependency on controller-runtime
2 parents 6005d29 + 5547262 commit a8e0815

File tree

21 files changed

+2092
-306
lines changed

21 files changed

+2092
-306
lines changed

.github/workflows/build-test.yaml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,36 @@
11
---
22
name: "Build & Test"
3-
on: # yamllint disable-line rule:truthy
3+
on: # yamllint disable-line rule:truthy
44
push:
55
branches:
66
- "main"
77
pull_request:
88
branches:
99
- "*"
10+
env:
11+
GO_VERSION: "~1.20.7"
1012
jobs:
1113
unit:
1214
name: "Unit"
1315
runs-on: "ubuntu-latest"
1416
steps:
1517
- uses: "actions/checkout@v3"
16-
- uses: "actions/setup-go@v3"
18+
- uses: "authzed/actions/setup-go@main"
1719
with:
18-
go-version: "~1.20"
19-
- uses: "authzed/actions/go-test@main"
20+
go-version: "${{ env.GO_VERSION }}"
21+
- uses: "magefile/mage-action@v2"
22+
with:
23+
version: "latest"
24+
args: "test:unit"
25+
integration:
26+
name: "integration"
27+
runs-on: "ubuntu-latest"
28+
steps:
29+
- uses: "actions/checkout@v3"
30+
- uses: "authzed/actions/setup-go@main"
31+
with:
32+
go-version: "${{ env.GO_VERSION }}"
33+
- uses: "magefile/mage-action@v2"
34+
with:
35+
version: "latest"
36+
args: "test:integration"

.github/workflows/lint.yaml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,34 @@ on: # yamllint disable-line rule:truthy
77
- "main"
88
pull_request:
99
branches: ["*"]
10+
env:
11+
GO_VERSION: "~1.20.7"
1012
jobs:
1113
go-lint:
1214
name: "Lint Go"
1315
runs-on: "ubuntu-latest"
1416
steps:
1517
- uses: "actions/checkout@v3"
16-
- uses: "actions/setup-go@v3"
18+
- uses: "authzed/actions/setup-go@main"
1719
with:
18-
go-version: "~1.20"
19-
- uses: "authzed/actions/gofumpt@main"
20-
- uses: "authzed/actions/go-generate@main"
20+
go-version: "${{ env.GO_VERSION }}"
21+
- uses: "magefile/mage-action@v2"
22+
with:
23+
version: "latest"
24+
args: "lint:go"
25+
# golangci-lint is kept out of the module for now, since it needs to be
26+
# run from root and would pollute the module's dependencies
2127
- uses: "authzed/actions/golangci-lint@main"
2228

2329
extra-lint:
2430
name: "Lint YAML & Markdown"
2531
runs-on: "ubuntu-latest"
2632
steps:
2733
- uses: "actions/checkout@v3"
28-
- uses: "authzed/actions/yaml-lint@main"
29-
- uses: "authzed/actions/markdown-lint@main"
34+
- uses: "authzed/actions/setup-go@main"
35+
with:
36+
go-version: "${{ env.GO_VERSION }}"
37+
- uses: "magefile/mage-action@v2"
38+
with:
39+
version: "latest"
40+
args: "lint:extra"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.kubeconfig

bootstrap/crds.go

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
package bootstrap
22

33
import (
4+
"context"
5+
"fmt"
46
"io/fs"
57
"path"
68
"time"
79

8-
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
10+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
11+
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
12+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/apimachinery/pkg/runtime/schema"
15+
"k8s.io/apimachinery/pkg/util/wait"
916
"k8s.io/apimachinery/pkg/util/yaml"
17+
"k8s.io/client-go/discovery"
1018
"k8s.io/client-go/rest"
11-
"sigs.k8s.io/controller-runtime/pkg/envtest"
19+
"k8s.io/client-go/util/retry"
1220
)
1321

1422
const (
@@ -18,15 +26,21 @@ const (
1826
)
1927

2028
// CRD installs the CRDs in the filesystem into the kube cluster configured by the rest config.
29+
// Deprecated: Use CRDs instead.
2130
func CRD(restConfig *rest.Config, crdFS fs.ReadDirFS, dir string) error {
22-
crds := make([]*v1.CustomResourceDefinition, 0)
31+
return CRDs(context.Background(), restConfig, crdFS, dir)
32+
}
33+
34+
// CRDs installs the CRDs in the filesystem into the kube cluster configured by the rest config.
35+
func CRDs(ctx context.Context, restConfig *rest.Config, crdFS fs.ReadDirFS, dir string) error {
36+
crds := make([]*apiextensionsv1.CustomResourceDefinition, 0)
2337

2438
crdFiles, err := crdFS.ReadDir(dir)
2539
if err != nil {
2640
return err
2741
}
2842
for _, crdFile := range crdFiles {
29-
var crd v1.CustomResourceDefinition
43+
var crd apiextensionsv1.CustomResourceDefinition
3044
file, err := crdFS.Open(path.Join(dir, crdFile.Name()))
3145
if err != nil {
3246
return err
@@ -37,11 +51,87 @@ func CRD(restConfig *rest.Config, crdFS fs.ReadDirFS, dir string) error {
3751
crds = append(crds, &crd)
3852
}
3953

40-
_, err = envtest.InstallCRDs(restConfig, envtest.CRDInstallOptions{
41-
CRDs: crds,
42-
MaxTime: maxCRDInstallTime,
43-
PollInterval: crdInstallPollInterval,
44-
CleanUpAfterUse: false,
45-
})
54+
if err := createCRDs(ctx, restConfig, crds); err != nil {
55+
return err
56+
}
57+
58+
if err := waitForDiscovery(ctx, restConfig, crds); err != nil {
59+
return err
60+
}
61+
4662
return err
4763
}
64+
65+
// createCRDs creates (or updates) CRDs in the cluster
66+
func createCRDs(ctx context.Context, config *rest.Config, crds []*apiextensionsv1.CustomResourceDefinition) error {
67+
c, err := clientset.NewForConfig(config)
68+
if err != nil {
69+
return err
70+
}
71+
crdClient := c.ApiextensionsV1().CustomResourceDefinitions()
72+
for _, crd := range crds {
73+
crd := crd
74+
_, err = crdClient.Get(ctx, crd.GetName(), metav1.GetOptions{})
75+
if k8serrors.IsNotFound(err) {
76+
if _, err := crdClient.Create(ctx, crd, metav1.CreateOptions{}); err != nil {
77+
return fmt.Errorf("unable to create CRD %q: %w", crd.Name, err)
78+
}
79+
continue
80+
}
81+
if err != nil {
82+
return fmt.Errorf("failed when fetching CRD %q: %w", crd.Name, err)
83+
}
84+
85+
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
86+
got, err := crdClient.Get(ctx, crd.Name, metav1.GetOptions{})
87+
if err != nil {
88+
return err
89+
}
90+
crd.SetResourceVersion(got.GetResourceVersion())
91+
_, err = crdClient.Update(ctx, crd, metav1.UpdateOptions{})
92+
return err
93+
}); err != nil {
94+
return err
95+
}
96+
}
97+
return nil
98+
}
99+
100+
func waitForDiscovery(ctx context.Context, config *rest.Config, crds []*apiextensionsv1.CustomResourceDefinition) error {
101+
gvrs := map[schema.GroupVersionResource]struct{}{}
102+
for _, crd := range crds {
103+
for _, version := range crd.Spec.Versions {
104+
if !version.Served {
105+
continue
106+
}
107+
gvrs[schema.GroupVersionResource{
108+
Group: crd.Spec.Group,
109+
Version: version.Name,
110+
Resource: crd.Spec.Names.Plural,
111+
}] = struct{}{}
112+
}
113+
}
114+
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
115+
if err != nil {
116+
return err
117+
}
118+
119+
return wait.PollUntilContextTimeout(ctx, crdInstallPollInterval, maxCRDInstallTime, true, func(ctx context.Context) (done bool, err error) {
120+
_, serverGVRs, err := discoveryClient.ServerGroupsAndResources()
121+
if err != nil {
122+
return false, nil
123+
}
124+
125+
for _, gv := range serverGVRs {
126+
for _, r := range gv.APIResources {
127+
delete(gvrs, schema.GroupVersionResource{
128+
Group: r.Group,
129+
Version: r.Version,
130+
Resource: r.Name,
131+
})
132+
}
133+
}
134+
135+
return len(gvrs) == 0, nil
136+
})
137+
}

bootstrap/crds_e2e_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//go:build e2e
2+
3+
package bootstrap
4+
5+
import (
6+
"context"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12+
"k8s.io/apimachinery/pkg/runtime/schema"
13+
"k8s.io/cli-runtime/pkg/genericclioptions"
14+
cmdutil "k8s.io/kubectl/pkg/cmd/util"
15+
"k8s.io/utils/pointer"
16+
)
17+
18+
func TestCRD(t *testing.T) {
19+
opts := genericclioptions.NewConfigFlags(true)
20+
opts.KubeConfig = pointer.String("../controller-idioms-e2e.kubeconfig")
21+
factory := cmdutil.NewFactory(opts)
22+
restConfig, err := factory.ToRESTConfig()
23+
require.NoError(t, err)
24+
25+
// ensure CRDs
26+
require.NoError(t, CRDs(context.Background(), restConfig, crdFS, "example"))
27+
28+
// create an object
29+
client, err := factory.DynamicClient()
30+
require.NoError(t, err)
31+
_, err = client.Resource(schema.GroupVersionResource{
32+
Group: "example.com",
33+
Version: "v1",
34+
Resource: "mytypes",
35+
}).Namespace("default").Create(context.Background(), &unstructured.Unstructured{Object: map[string]any{
36+
"kind": "MyType",
37+
"apiVersion": "example.com/v1",
38+
"metadata": map[string]any{"generateName": "test"},
39+
}}, metav1.CreateOptions{})
40+
require.NoError(t, err)
41+
}

bootstrap/crds_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package bootstrap
22

33
import (
4+
"context"
45
"embed"
56

67
"k8s.io/client-go/rest"
@@ -10,6 +11,6 @@ import (
1011
var crdFS embed.FS
1112

1213
func ExampleCRD() {
13-
_ = CRD(&rest.Config{}, crdFS, "example")
14+
_ = CRDs(context.Background(), &rest.Config{}, crdFS, "example")
1415
// Output:
1516
}

bootstrap/example/crd.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
apiVersion: "apiextensions.k8s.io/v1"
33
kind: "CustomResourceDefinition"
44
metadata:
5-
name: "mytype.example.com"
5+
name: "mytypes.example.com"
66
spec:
77
group: "example.com"
88
names:
@@ -15,3 +15,6 @@ spec:
1515
- name: "v1"
1616
served: true
1717
storage: true
18+
schema:
19+
openAPIV3Schema:
20+
type: "object"

bootstrap/resource.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package bootstrap
1515
import (
1616
"bytes"
1717
"context"
18+
"encoding/json"
1819
"errors"
1920
"io"
2021
"os"
@@ -29,7 +30,6 @@ import (
2930
"k8s.io/apimachinery/pkg/util/yaml"
3031
"k8s.io/client-go/dynamic"
3132
"k8s.io/utils/pointer"
32-
"sigs.k8s.io/controller-runtime/pkg/client"
3333
)
3434

3535
// KubeResourceObject is satisfied by any standard kube object.
@@ -78,7 +78,7 @@ func ResourceFromFile[O KubeResourceObject](ctx context.Context, fieldManager st
7878
return hash, err
7979
}
8080

81-
data, err := client.Apply.Data(objectDef)
81+
data, err := json.Marshal(objectDef)
8282
if err != nil {
8383
return hash, err
8484
}

0 commit comments

Comments
 (0)