Skip to content

Commit 14ec08e

Browse files
committed
undent: last line's tabs weren't stripped
1 parent 726bb7e commit 14ec08e

File tree

1 file changed

+328
-0
lines changed

1 file changed

+328
-0
lines changed

pkg/testutil/testutil.go

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
package testutil
2+
3+
import (
4+
"crypto/x509"
5+
"io"
6+
"net/http"
7+
"net/http/httptest"
8+
"os"
9+
"strings"
10+
"sync"
11+
"testing"
12+
13+
"github.com/jetstack/venafi-connection-lib/api/v1alpha1"
14+
"github.com/stretchr/testify/require"
15+
corev1 "k8s.io/api/core/v1"
16+
rbacv1 "k8s.io/api/rbac/v1"
17+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
18+
"k8s.io/apimachinery/pkg/runtime"
19+
"k8s.io/apimachinery/pkg/util/yaml"
20+
"k8s.io/client-go/rest"
21+
"k8s.io/client-go/tools/clientcmd"
22+
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
23+
ctrlruntime "sigs.k8s.io/controller-runtime/pkg/client"
24+
"sigs.k8s.io/controller-runtime/pkg/envtest"
25+
)
26+
27+
// To see the API server logs, set:
28+
//
29+
// export KUBEBUILDER_ATTACH_CONTROL_PLANE_OUTPUT=true
30+
func WithEnvtest(t testing.TB) (_ *envtest.Environment, _ *rest.Config, kclient ctrlruntime.WithWatch) {
31+
t.Helper()
32+
33+
// If KUBEBUILDER_ASSETS isn't set, show a warning to the user.
34+
if os.Getenv("KUBEBUILDER_ASSETS") == "" {
35+
t.Fatalf("KUBEBUILDER_ASSETS isn't set. You can run this test using `make test`.\n" +
36+
"But if you prefer not to use `make`, run these two commands first:\n" +
37+
" make _bin/tools/{kube-apiserver,etcd}\n" +
38+
" export KUBEBUILDER_ASSETS=$PWD/_bin/tools")
39+
}
40+
envtest := &envtest.Environment{
41+
ErrorIfCRDPathMissing: true,
42+
CRDDirectoryPaths: []string{"../../deploy/charts/venafi-kubernetes-agent/crd_bases/jetstack.io_venaficonnections.yaml"},
43+
}
44+
45+
restconf, err := envtest.Start()
46+
t.Cleanup(func() {
47+
t.Log("Waiting for envtest to exit")
48+
e := envtest.Stop()
49+
require.NoError(t, e)
50+
})
51+
require.NoError(t, err)
52+
53+
sch := runtime.NewScheme()
54+
_ = v1alpha1.AddToScheme(sch)
55+
_ = corev1.AddToScheme(sch)
56+
_ = rbacv1.AddToScheme(sch)
57+
58+
kclient, err = ctrlruntime.NewWithWatch(restconf, ctrlruntime.Options{Scheme: sch})
59+
require.NoError(t, err)
60+
61+
return envtest, restconf, kclient
62+
}
63+
64+
// Copied from https://github.com/kubernetes/client-go/issues/711#issuecomment-1666075787.
65+
func WithKubeconfig(t testing.TB, restCfg *rest.Config) string {
66+
t.Helper()
67+
68+
clusters := make(map[string]*clientcmdapi.Cluster)
69+
clusters["default-cluster"] = &clientcmdapi.Cluster{
70+
Server: restCfg.Host,
71+
CertificateAuthorityData: restCfg.CAData,
72+
}
73+
contexts := make(map[string]*clientcmdapi.Context)
74+
contexts["default-context"] = &clientcmdapi.Context{
75+
Cluster: "default-cluster",
76+
AuthInfo: "default-user",
77+
}
78+
authinfos := make(map[string]*clientcmdapi.AuthInfo)
79+
authinfos["default-user"] = &clientcmdapi.AuthInfo{
80+
ClientCertificateData: restCfg.CertData,
81+
ClientKeyData: restCfg.KeyData,
82+
}
83+
clientConfig := clientcmdapi.Config{
84+
Kind: "Config",
85+
APIVersion: "v1",
86+
Clusters: clusters,
87+
Contexts: contexts,
88+
CurrentContext: "default-context",
89+
AuthInfos: authinfos,
90+
}
91+
92+
d := t.TempDir()
93+
kubeconfig, _ := os.CreateTemp(d, "kubeconfig")
94+
defer kubeconfig.Close()
95+
96+
err := clientcmd.WriteToFile(clientConfig, kubeconfig.Name())
97+
require.NoError(t, err)
98+
99+
return kubeconfig.Name()
100+
}
101+
102+
// Undent removes leading indentation/white-space from given string and returns
103+
// it as a string. Useful for inlining YAML manifests in Go code. Inline YAML
104+
// manifests in the Go test files makes it easier to read the test case as
105+
// opposed to reading verbose-y Go structs.
106+
//
107+
// This was copied from https://github.com/jimeh/Undent/blob/main/Undent.go, all
108+
// credit goes to the author, Jim Myhrberg.
109+
func Undent(s string) string {
110+
const (
111+
tab = 9
112+
lf = 10
113+
spc = 32
114+
)
115+
116+
if len(s) == 0 {
117+
return ""
118+
}
119+
120+
// find smallest indent relative to each line-feed
121+
min := 99999999999
122+
count := 0
123+
124+
lfs := make([]int, 0, strings.Count(s, "\n"))
125+
if s[0] != lf {
126+
lfs = append(lfs, -1)
127+
}
128+
129+
indent := 0
130+
for i := 0; i < len(s); i++ {
131+
if s[i] == lf {
132+
lfs = append(lfs, i)
133+
indent = 0
134+
} else if indent < min {
135+
switch s[i] {
136+
case spc, tab:
137+
indent++
138+
default:
139+
if indent > 0 {
140+
count++
141+
}
142+
if indent < min {
143+
min = indent
144+
}
145+
}
146+
}
147+
}
148+
149+
// extract each line without indentation
150+
out := make([]byte, 0, len(s)-(min*count))
151+
152+
for i := 0; i < len(lfs); i++ {
153+
offset := lfs[i] + 1
154+
end := len(s)
155+
if i+1 < len(lfs) {
156+
end = lfs[i+1] + 1
157+
}
158+
159+
if offset+min <= end {
160+
out = append(out, s[offset+min:end]...)
161+
} else if offset < end {
162+
out = append(out, s[offset:end]...)
163+
}
164+
}
165+
166+
return string(out)
167+
}
168+
169+
// Parses the YAML manifest. Useful for inlining YAML manifests in Go test
170+
// files, to be used in conjunction with `undent`.
171+
func Parse(yamlmanifest string) []ctrlruntime.Object {
172+
dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(yamlmanifest), 4096)
173+
var objs []ctrlruntime.Object
174+
for {
175+
obj := &unstructured.Unstructured{}
176+
err := dec.Decode(obj)
177+
if err == io.EOF {
178+
break
179+
}
180+
if err != nil {
181+
panic(err)
182+
}
183+
184+
objs = append(objs, obj)
185+
}
186+
return objs
187+
}
188+
189+
type AssertRequest func(t testing.TB, r *http.Request)
190+
191+
func FakeVenafiCloud(t *testing.T) (_ *httptest.Server, _ *x509.Certificate, setAssert func(AssertRequest)) {
192+
t.Helper()
193+
194+
var assertFn AssertRequest
195+
assertFnMu := sync.Mutex{}
196+
setAssert = func(setAssert AssertRequest) {
197+
assertFnMu.Lock()
198+
defer assertFnMu.Unlock()
199+
assertFn = setAssert
200+
}
201+
202+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
203+
t.Logf("fake api.venafi.cloud received request: %s %s", r.Method, r.URL.Path)
204+
205+
assertFnMu.Lock()
206+
defer assertFnMu.Unlock()
207+
assertFn(t, r)
208+
209+
accessToken := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
210+
apiKey := r.Header.Get("tppl-api-key")
211+
if accessToken != "VALID_ACCESS_TOKEN" && apiKey != "VALID_API_KEY" {
212+
w.WriteHeader(http.StatusUnauthorized)
213+
return
214+
}
215+
if r.URL.Path == "/v1/tlspk/upload/clusterdata/no" {
216+
if r.URL.Query().Get("name") != "test cluster name" {
217+
w.WriteHeader(http.StatusBadRequest)
218+
return
219+
}
220+
_, _ = w.Write([]byte(`{"status":"ok","organization":"756db001-280e-11ee-84fb-991f3177e2d0"}`))
221+
} else if r.URL.Path == "/v1/useraccounts" {
222+
w.WriteHeader(http.StatusOK)
223+
_, _ = w.Write([]byte(`{"user": {"username": "user","id": "76a126f0-280e-11ee-84fb-991f3177e2d0"}}`))
224+
225+
} else if r.URL.Path == "/v1/oauth2/v2.0/756db001-280e-11ee-84fb-991f3177e2d0/token" {
226+
_, _ = w.Write([]byte(`{"access_token":"VALID_ACCESS_TOKEN","expires_in":900,"token_type":"bearer"}`))
227+
} else {
228+
w.WriteHeader(http.StatusInternalServerError)
229+
_, _ = w.Write([]byte(`{"error":"unexpected path in the test server","path":"` + r.URL.Path + `"}`))
230+
}
231+
}))
232+
t.Cleanup(server.Close)
233+
234+
cert, err := x509.ParseCertificate(server.TLS.Certificates[0].Certificate[0])
235+
require.NoError(t, err)
236+
237+
return server, cert, setAssert
238+
}
239+
240+
func FakeTPP(t testing.TB) (*httptest.Server, *x509.Certificate) {
241+
t.Helper()
242+
243+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
244+
t.Logf("fake tpp.example.com received request: %s %s", r.Method, r.URL.Path)
245+
246+
accessToken := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
247+
248+
if r.URL.Path == "/vedsdk/Identity/Self" {
249+
if accessToken != "VALID_ACCESS_TOKEN" {
250+
w.WriteHeader(http.StatusUnauthorized)
251+
return
252+
}
253+
_, _ = w.Write([]byte(`{"Identities":[{"Name":"TEST"}]}`))
254+
} else if r.URL.Path == "/vedsdk/certificates/checkpolicy" {
255+
_, _ = w.Write([]byte(`{"Policy":{"Subject":{"Organization":{"Value": "test-org"}}}}`))
256+
} else {
257+
w.WriteHeader(http.StatusInternalServerError)
258+
_, _ = w.Write([]byte(`{"error":"unexpected path in the test server","path":"` + r.URL.Path + `"}`))
259+
}
260+
}))
261+
t.Cleanup(server.Close)
262+
263+
cert, err := x509.ParseCertificate(server.TLS.Certificates[0].Certificate[0])
264+
require.NoError(t, err)
265+
266+
return server, cert
267+
}
268+
269+
// Generated using:
270+
//
271+
// helm template ./deploy/charts/venafi-kubernetes-agent -n venafi --set venafiConnection.include=true --show-only templates/venafi-connection-VenConnRBAC.yaml | grep -ivE '(helm|\/version)'
272+
const VenConnRBAC = `
273+
apiVersion: v1
274+
kind: Namespace
275+
metadata:
276+
name: venafi
277+
---
278+
# Source: venafi-kubernetes-agent/templates/venafi-connection-rbac.yaml
279+
# The 'venafi-connection' service account is used by multiple
280+
# controllers. When configuring which resources a VenafiConnection
281+
# can access, the RBAC rules you create manually must point to this SA.
282+
apiVersion: v1
283+
kind: ServiceAccount
284+
metadata:
285+
name: venafi-connection
286+
namespace: "venafi"
287+
labels:
288+
app.kubernetes.io/name: "venafi-connection"
289+
app.kubernetes.io/instance: release-name
290+
---
291+
# Source: venafi-kubernetes-agent/templates/venafi-connection-rbac.yaml
292+
apiVersion: rbac.authorization.k8s.io/v1
293+
kind: ClusterRole
294+
metadata:
295+
name: venafi-connection-role
296+
labels:
297+
app.kubernetes.io/name: "venafi-connection"
298+
app.kubernetes.io/instance: release-name
299+
rules:
300+
- apiGroups: [ "" ]
301+
resources: [ "namespaces" ]
302+
verbs: [ "get", "list", "watch" ]
303+
304+
- apiGroups: [ "jetstack.io" ]
305+
resources: [ "venaficonnections" ]
306+
verbs: [ "get", "list", "watch" ]
307+
308+
- apiGroups: [ "jetstack.io" ]
309+
resources: [ "venaficonnections/status" ]
310+
verbs: [ "get", "patch" ]
311+
---
312+
# Source: venafi-kubernetes-agent/templates/venafi-connection-rbac.yaml
313+
apiVersion: rbac.authorization.k8s.io/v1
314+
kind: ClusterRoleBinding
315+
metadata:
316+
name: venafi-connection-rolebinding
317+
labels:
318+
app.kubernetes.io/name: "venafi-connection"
319+
app.kubernetes.io/instance: release-name
320+
roleRef:
321+
apiGroup: rbac.authorization.k8s.io
322+
kind: ClusterRole
323+
name: venafi-connection-role
324+
subjects:
325+
- kind: ServiceAccount
326+
name: venafi-connection
327+
namespace: "venafi"
328+
`

0 commit comments

Comments
 (0)