`.
100 | There is therefore a high coupling between the library name and the path to the library in
101 | the git repository. The library name is also used as the name of the library in Styra DAS.
102 | With the above example, the content of the library would be the files found at
103 | `https://github.com/Bankdata/styra-controller/tree/master/rego/path/libraries/mylibrary`
104 | together with the datasource.
--------------------------------------------------------------------------------
/docs/images/Styra/system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bankdata/styra-controller/52f1bb445774905788e4620cde18be99d2e0e870/docs/images/Styra/system.png
--------------------------------------------------------------------------------
/docs/images/controller-arch.dark.excalidraw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bankdata/styra-controller/52f1bb445774905788e4620cde18be99d2e0e870/docs/images/controller-arch.dark.excalidraw.png
--------------------------------------------------------------------------------
/docs/images/controller-arch.light.excalidraw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bankdata/styra-controller/52f1bb445774905788e4620cde18be99d2e0e870/docs/images/controller-arch.light.excalidraw.png
--------------------------------------------------------------------------------
/docs/installation.md:
--------------------------------------------------------------------------------
1 | # Compatibility Table
2 |
3 | | Styra Controller | Styra DAS SaaS | Styra DAS Self-Hosted |
4 | |------------------|----------------|-----------------------|
5 | | v0.1.0 | 20230125 | 0.10.2 |
6 |
7 |
8 | # Configuration of the Styra Controller
9 | The [configuration](https://github.com/Bankdata/styra-controller/blob/master/docs/configuration.md) document contains information about the configuration options for the Styra
10 | Controller.
11 |
--------------------------------------------------------------------------------
/docs/releasing.md:
--------------------------------------------------------------------------------
1 | # Releasing
2 |
3 | Only maintainers with access to pushing tags are able to perform releases. If
4 | changes have been merged into the master branch but a release has not yet been
5 | scheduled, you can contact one of the maintainers to request and plan the
6 | release.
7 |
8 | ## Binaries and docker images
9 |
10 | In order to make a new release push a semver tag eg. `v0.1.0`. If you want to
11 | publish a prerelase, simply do a prerelease tag eg. `v0.2.0-rc.1`.
12 |
13 | This will run [goreleaser](https://goreleaser.com/) according to the
14 | configuration in `.goreleaser.yaml`.
15 |
--------------------------------------------------------------------------------
/hack/boilerplate.go.txt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 |
--------------------------------------------------------------------------------
/internal/config/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 config provides utilities for reading configfiles
18 | package config
19 |
20 | import (
21 | "os"
22 | "regexp"
23 |
24 | "github.com/bankdata/styra-controller/api/config/v2alpha2"
25 | "github.com/pkg/errors"
26 | "k8s.io/apimachinery/pkg/runtime"
27 | "k8s.io/apimachinery/pkg/runtime/serializer"
28 | "sigs.k8s.io/controller-runtime/pkg/manager"
29 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
30 | "sigs.k8s.io/controller-runtime/pkg/webhook"
31 | )
32 |
33 | const (
34 | healthProbeBindAddress = ":8081"
35 | metricsBindAddress = ":8080"
36 | leaderElectionID = "5d272013.bankdata.dk"
37 | webhookPort = 9443
38 | )
39 |
40 | // Load loads controller configuration from the given file using the types
41 | // registered in the scheme.
42 | func Load(file string, scheme *runtime.Scheme) (*v2alpha2.ProjectConfig, error) {
43 | bs, err := os.ReadFile(file)
44 | if err != nil {
45 | return nil, errors.Wrap(err, "could not read config file")
46 | }
47 | return deserialize(bs, scheme)
48 | }
49 |
50 | // OptionsFromConfig creates a manager.Options based on a configuration file
51 | func OptionsFromConfig(cfg *v2alpha2.ProjectConfig, scheme *runtime.Scheme) manager.Options {
52 | o := manager.Options{
53 | Scheme: scheme,
54 | HealthProbeBindAddress: healthProbeBindAddress,
55 | WebhookServer: webhook.NewServer(webhook.Options{Port: webhookPort}),
56 | Metrics: metricsserver.Options{
57 | BindAddress: metricsBindAddress,
58 | },
59 | }
60 |
61 | if cfg.LeaderElection != nil {
62 | o.LeaderElection = true
63 | o.LeaseDuration = &cfg.LeaderElection.LeaseDuration.Duration
64 | o.RenewDeadline = &cfg.LeaderElection.RenewDeadline.Duration
65 | o.RetryPeriod = &cfg.LeaderElection.RetryPeriod.Duration
66 | o.LeaderElectionID = leaderElectionID
67 | }
68 |
69 | return o
70 | }
71 |
72 | // TokenFromConfig returns the Styra DAS api token directly from "styra.token"
73 | // in the config or using the "styra.tokenSecretPath" to retrieve it fra a secret
74 | func TokenFromConfig(cfg *v2alpha2.ProjectConfig) (string, error) {
75 | if cfg.Styra.Token != "" {
76 | return cfg.Styra.Token, nil
77 | }
78 |
79 | if cfg.Styra.TokenSecretPath != "" {
80 | styraURLBytes, err := os.ReadFile(cfg.Styra.TokenSecretPath)
81 | if err != nil {
82 | return "", errors.Wrapf(err, "Could not ready Styra token from TokenSecretPath: %s", cfg.Styra.TokenSecretPath)
83 | }
84 | return string(styraURLBytes), nil
85 | }
86 |
87 | return "", errors.New("No token or tokenSecretPath defined in the config")
88 | }
89 |
90 | func deserialize(data []byte, scheme *runtime.Scheme) (*v2alpha2.ProjectConfig, error) {
91 | decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer()
92 | _, gvk, err := decoder.Decode(data, nil, nil)
93 | if err != nil {
94 | return nil, errors.Wrap(err, "could not decode config")
95 | }
96 |
97 | if gvk.Group != v2alpha2.GroupVersion.Group {
98 | return nil, errors.New("unsupported api group")
99 | }
100 |
101 | if gvk.Kind != "ProjectConfig" {
102 | return nil, errors.New("unsupported api kind")
103 | }
104 |
105 | cfg := &v2alpha2.ProjectConfig{}
106 |
107 | switch gvk.Version {
108 | case v2alpha2.GroupVersion.Version:
109 | if _, _, err := decoder.Decode(data, nil, cfg); err != nil {
110 | return nil, errors.Wrap(err, "could not decode into kind")
111 | }
112 | default:
113 | return nil, errors.New("unsupported api version")
114 | }
115 |
116 | return cfg, nil
117 | }
118 |
119 | // MatchesIgnorePattern matches a specified ignore pattern, and excludes matches from being deleted
120 | func MatchesIgnorePattern(ignorePatterns []string, id string) (bool, error) {
121 | for _, patternString := range ignorePatterns {
122 | matches, err := regexp.MatchString(patternString, id)
123 | if err != nil {
124 | return false, errors.Wrapf(err, "could not compile regex pattern: %s", patternString)
125 | }
126 | if matches {
127 | return true, nil
128 | }
129 | }
130 | return false, nil
131 | }
132 |
--------------------------------------------------------------------------------
/internal/config/config_suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 config
18 |
19 | import (
20 | "testing"
21 |
22 | ginkgo "github.com/onsi/ginkgo/v2"
23 | gomega "github.com/onsi/gomega"
24 | )
25 |
26 | func TestConfig(t *testing.T) {
27 | gomega.RegisterFailHandler(ginkgo.Fail)
28 | ginkgo.RunSpecs(t, "internal/config")
29 | }
30 |
--------------------------------------------------------------------------------
/internal/config/config_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 config
18 |
19 | import (
20 | ginkgo "github.com/onsi/ginkgo/v2"
21 | gomega "github.com/onsi/gomega"
22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23 | "k8s.io/apimachinery/pkg/runtime"
24 |
25 | "github.com/bankdata/styra-controller/api/config/v2alpha2"
26 | )
27 |
28 | var _ = ginkgo.DescribeTable("deserialize",
29 | func(data []byte, expected *v2alpha2.ProjectConfig, shouldErr bool) {
30 | scheme := runtime.NewScheme()
31 | err := v2alpha2.AddToScheme(scheme)
32 | gomega.Ω(err).ShouldNot(gomega.HaveOccurred())
33 | actual, err := deserialize(data, scheme)
34 | if shouldErr {
35 | gomega.Ω(err).Should(gomega.HaveOccurred())
36 | } else {
37 | gomega.Ω(err).ShouldNot(gomega.HaveOccurred())
38 | }
39 | gomega.Ω(actual).Should(gomega.Equal(expected))
40 | },
41 |
42 | ginkgo.Entry("errors on unexpected api group",
43 | []byte(`
44 | apiVersion: myconfig.bankdata.dk/v1
45 | kind: ProjectConfig
46 | styra:
47 | token: my-token
48 | `),
49 | nil,
50 | true,
51 | ),
52 |
53 | ginkgo.Entry("can deserialize v2alpha2",
54 | []byte(`
55 | apiVersion: config.bankdata.dk/v2alpha2
56 | kind: ProjectConfig
57 | styra:
58 | token: my-token
59 | `),
60 | &v2alpha2.ProjectConfig{
61 | TypeMeta: metav1.TypeMeta{
62 | Kind: "ProjectConfig",
63 | APIVersion: v2alpha2.GroupVersion.Identifier(),
64 | },
65 | Styra: v2alpha2.StyraConfig{
66 | Token: "my-token",
67 | },
68 | },
69 | false,
70 | ),
71 | )
72 |
--------------------------------------------------------------------------------
/internal/controller/styra/styra.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2023 Bankdata.
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 styra holds controllers for the styra API group.
18 | package styra
19 |
--------------------------------------------------------------------------------
/internal/controller/styra/styra_suite_test.go:
--------------------------------------------------------------------------------
1 | package styra_test
2 |
3 | import (
4 | "testing"
5 |
6 | ginkgo "github.com/onsi/ginkgo/v2"
7 | gomega "github.com/onsi/gomega"
8 | )
9 |
10 | func TestStyra(t *testing.T) {
11 | gomega.RegisterFailHandler(ginkgo.Fail)
12 | ginkgo.RunSpecs(t, "internal/controller/styra")
13 | }
14 |
--------------------------------------------------------------------------------
/internal/controller/styra/system_controller_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra
18 |
19 | import (
20 | ginkgo "github.com/onsi/ginkgo/v2"
21 | gomega "github.com/onsi/gomega"
22 |
23 | configv2alpha2 "github.com/bankdata/styra-controller/api/config/v2alpha2"
24 | styrav1beta1 "github.com/bankdata/styra-controller/api/styra/v1beta1"
25 | "github.com/bankdata/styra-controller/pkg/styra"
26 | )
27 |
28 | var _ = ginkgo.DescribeTable("createRolebindingSubjects",
29 | func(subjects []styrav1beta1.Subject, expectedSubject []*styra.Subject) {
30 | gomega.Ω(createRolebindingSubjects(subjects, &configv2alpha2.SSOConfig{
31 | IdentityProvider: "BDAD",
32 | JWTGroupsClaim: "groups",
33 | })).To(gomega.Equal(expectedSubject))
34 | },
35 |
36 | ginkgo.Entry("returns same adgroup",
37 | []styrav1beta1.Subject{{Kind: "group", Name: "ADTEST"}},
38 | []*styra.Subject{{
39 | Kind: "claim",
40 | ClaimConfig: &styra.ClaimConfig{
41 | IdentityProvider: "BDAD",
42 | Key: "groups",
43 | Value: "ADTEST",
44 | },
45 | }},
46 | ),
47 |
48 | ginkgo.Entry("defaults empty kind value to user",
49 | []styrav1beta1.Subject{
50 | {Kind: "user", Name: "test1@test.dk"},
51 | {Name: "test2@test.dk"},
52 | {Kind: "group", Name: "ADTEST"},
53 | },
54 | []*styra.Subject{
55 | {
56 | Kind: "user",
57 | ID: "test1@test.dk",
58 | }, {
59 | Kind: "user",
60 | ID: "test2@test.dk",
61 | }, {
62 | Kind: "claim",
63 | ClaimConfig: &styra.ClaimConfig{
64 | IdentityProvider: "BDAD",
65 | Key: "groups",
66 | Value: "ADTEST",
67 | },
68 | },
69 | },
70 | ),
71 |
72 | ginkgo.Entry("does not return duplicates adgroups",
73 | []styrav1beta1.Subject{
74 | {Kind: "group", Name: "ADTEST"},
75 | {Kind: "group", Name: "ADTEST1"},
76 | {Kind: "group", Name: "ADTEST1"},
77 | },
78 | []*styra.Subject{
79 | {
80 | Kind: "claim",
81 | ClaimConfig: &styra.ClaimConfig{
82 | IdentityProvider: "BDAD",
83 | Key: "groups",
84 | Value: "ADTEST",
85 | },
86 | }, {
87 | Kind: "claim",
88 | ClaimConfig: &styra.ClaimConfig{
89 | IdentityProvider: "BDAD",
90 | Key: "groups",
91 | Value: "ADTEST1",
92 | },
93 | },
94 | },
95 | ),
96 |
97 | ginkgo.Entry("does not return duplicates users and groups",
98 | []styrav1beta1.Subject{
99 | {Kind: "user", Name: "test@test.dk"},
100 | {Kind: "user", Name: "test@test.dk"},
101 | {Kind: "group", Name: "ADTEST"},
102 | {Kind: "group", Name: "ADTEST"},
103 | },
104 | []*styra.Subject{
105 | {
106 | Kind: "user",
107 | ID: "test@test.dk",
108 | }, {
109 | Kind: "claim",
110 | ClaimConfig: &styra.ClaimConfig{
111 | IdentityProvider: "BDAD",
112 | Key: "groups",
113 | Value: "ADTEST",
114 | },
115 | },
116 | },
117 | ),
118 | )
119 |
120 | // test the isURLValid method
121 | var _ = ginkgo.DescribeTable("isURLValid",
122 | func(url string, expected bool) {
123 | gomega.Ω(isURLValid(url)).To(gomega.Equal(expected))
124 | },
125 | ginkgo.Entry("valid url", "", true),
126 | ginkgo.Entry("valid url", "https://www.github.com/test/repo.git", true),
127 | ginkgo.Entry("valid url", "https://www.github.com/test/repo", true),
128 | ginkgo.Entry("invalid url", "https://www.github.com/[test]/repo", false),
129 | ginkgo.Entry("invalid url", "https://www.github.com/[test]/repo.git", false),
130 | ginkgo.Entry("invalid url", "www.google.com", false),
131 | ginkgo.Entry("invalid url", "google.com", false),
132 | ginkgo.Entry("invalid url", "google", false),
133 | )
134 |
--------------------------------------------------------------------------------
/internal/errors/errors.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 errors contains errors.
18 | package errors
19 |
20 | import (
21 | "github.com/pkg/errors"
22 |
23 | "github.com/bankdata/styra-controller/api/styra/v1beta1"
24 | )
25 |
26 | type stackTracer interface {
27 | StackTrace() errors.StackTrace
28 | }
29 |
30 | // ReconcilerErr defines an error that occurs during reconciliation.
31 | type ReconcilerErr struct {
32 | err error
33 | Event string
34 | ConditionType string
35 | }
36 |
37 | // New returns a new RenconcilerErr.
38 | func New(msg string) *ReconcilerErr {
39 | return Wrap(errors.New(msg), "")
40 | }
41 |
42 | // Wrap wraps an error as a RenconcilerErr.
43 | func Wrap(err error, msg string) *ReconcilerErr {
44 | return &ReconcilerErr{
45 | err: errors.Wrap(err, msg),
46 | }
47 | }
48 |
49 | // WithEvent adds event metadata to the ReconcilerErr.
50 | func (err *ReconcilerErr) WithEvent(event v1beta1.EventType) *ReconcilerErr {
51 | err.Event = string(event)
52 | return err
53 | }
54 |
55 | // WithSystemCondition adds condition metadata to the ReconcilerErr.
56 | func (err *ReconcilerErr) WithSystemCondition(contype v1beta1.ConditionType) *ReconcilerErr {
57 | err.ConditionType = string(contype)
58 | return err
59 | }
60 |
61 | // Error implements the error interface.
62 | func (err *ReconcilerErr) Error() string {
63 | return err.err.Error()
64 | }
65 |
66 | // Cause returns the cause of the error.
67 | func (err *ReconcilerErr) Cause() error {
68 | return err.err
69 | }
70 |
71 | // Unwrap is the same as `Cause()`
72 | func (err *ReconcilerErr) Unwrap() error {
73 | return err.Cause()
74 | }
75 |
76 | // StackTrace implements the stackTracer interface.
77 | func (err *ReconcilerErr) StackTrace() errors.StackTrace {
78 | var st stackTracer
79 | if errors.As(err.err, &st) {
80 | return st.StackTrace()
81 | }
82 | return nil
83 | }
84 |
--------------------------------------------------------------------------------
/internal/fields/fields.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 fields contains helpers for working with fields in the CRDs. These
18 | // are mostly used when setting up field indexers.
19 | package fields
20 |
21 | import "k8s.io/apimachinery/pkg/fields"
22 |
23 | const (
24 | // SystemCredentialsSecretName is the path to the credential secret name.
25 | SystemCredentialsSecretName = ".spec.sourceControl.origin.credentialsSecretName"
26 | )
27 |
28 | // SystemCredentialsSecretNameSelector returns a field selector for finding the
29 | // Systems referencing a secret.
30 | func SystemCredentialsSecretNameSelector(name string) fields.Selector {
31 | return fields.OneTermEqualSelector(SystemCredentialsSecretName, name)
32 | }
33 |
--------------------------------------------------------------------------------
/internal/finalizer/finalizer.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 finalizer contains helpers for working with the controller finalizer.
18 | package finalizer
19 |
20 | import (
21 | "sigs.k8s.io/controller-runtime/pkg/client"
22 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
23 | )
24 |
25 | const name = "styra.bankdata.dk/finalizer"
26 |
27 | // IsSet returns true if an object has the "styra.bankdata.dk/finalizer" finalizer.
28 | func IsSet(o client.Object) bool {
29 | return controllerutil.ContainsFinalizer(o, name)
30 | }
31 |
32 | // Add adds the "styra.bankdata.dk/finalizer" finalizer to an object.
33 | func Add(o client.Object) {
34 | controllerutil.AddFinalizer(o, name)
35 | }
36 |
37 | // Remove removes the "styra.bankdata.dk/finalizer" finalizer from an object.
38 | func Remove(o client.Object) {
39 | controllerutil.RemoveFinalizer(o, name)
40 | }
41 |
--------------------------------------------------------------------------------
/internal/k8sconv/suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 k8sconv_test
18 |
19 | import (
20 | "testing"
21 |
22 | ginkgo "github.com/onsi/ginkgo/v2"
23 | gomega "github.com/onsi/gomega"
24 | )
25 |
26 | func TestStyraClient(t *testing.T) {
27 | gomega.RegisterFailHandler(ginkgo.Fail)
28 | ginkgo.RunSpecs(t, "internal/k8sconv")
29 | }
30 |
--------------------------------------------------------------------------------
/internal/labels/labels.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 labels contains helpers for working with labels.
18 | package labels
19 |
20 | import (
21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22 | "k8s.io/apimachinery/pkg/labels"
23 | "sigs.k8s.io/controller-runtime/pkg/client"
24 | )
25 |
26 | const (
27 | labelControllerClass = "styra-controller/class"
28 | labelManagedBy = "app.kubernetes.io/managed-by"
29 | labelValueManagedBy = "styra-controller"
30 | )
31 |
32 | // ControllerClassLabelSelector creates a metav1.LabelSelector which selects
33 | // objects that has the "styra-controller/class" label with the value `class`.
34 | func ControllerClassLabelSelector(class string) metav1.LabelSelector {
35 | var selector metav1.LabelSelector
36 | if class != "" {
37 | selector = metav1.LabelSelector{
38 | MatchLabels: map[string]string{
39 | labelControllerClass: class,
40 | },
41 | }
42 | } else {
43 | selector = metav1.LabelSelector{
44 | MatchExpressions: []metav1.LabelSelectorRequirement{{
45 | Key: labelControllerClass,
46 | Operator: metav1.LabelSelectorOpDoesNotExist,
47 | }},
48 | }
49 | }
50 | return selector
51 | }
52 |
53 | // ControllerClassLabelSelectorAsSelector creates a labels.Selecter which
54 | // selects objects that has the "styra-controller/class" label with the value `class`.
55 | func ControllerClassLabelSelectorAsSelector(class string) (labels.Selector, error) {
56 | ls := ControllerClassLabelSelector(class)
57 | return metav1.LabelSelectorAsSelector(&ls)
58 | }
59 |
60 | // SetManagedBy sets the `app.kubernetes.io/managed-by` label to
61 | // styra-controller.
62 | func SetManagedBy(o client.Object) {
63 | labels := o.GetLabels()
64 | if labels == nil {
65 | labels = map[string]string{}
66 | }
67 | labels[labelManagedBy] = labelValueManagedBy
68 | o.SetLabels(labels)
69 | }
70 |
71 | // HasManagedBy checks if the object has the label `app.kubernetes.io/managed-by`
72 | // set to styra-controller
73 | func HasManagedBy(o client.Object) bool {
74 | managedBy, ok := o.GetLabels()[labelManagedBy]
75 | return ok && managedBy == labelValueManagedBy
76 | }
77 |
78 | // ControllerClassMatches checks if the object has the `styra-controller/class` label
79 | // with the value `class`.
80 | func ControllerClassMatches(o client.Object, class string) bool {
81 | labels := o.GetLabels()
82 | if labels == nil {
83 | return class == ""
84 | }
85 | return labels[labelControllerClass] == class
86 | }
87 |
--------------------------------------------------------------------------------
/internal/labels/labels_suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 labels_test
18 |
19 | import (
20 | "testing"
21 |
22 | ginkgo "github.com/onsi/ginkgo/v2"
23 | gomega "github.com/onsi/gomega"
24 | )
25 |
26 | func TestLabels(t *testing.T) {
27 | gomega.RegisterFailHandler(ginkgo.Fail)
28 | ginkgo.RunSpecs(t, "internal/labels")
29 | }
30 |
--------------------------------------------------------------------------------
/internal/labels/labels_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 labels_test
18 |
19 | import (
20 | ginkgo "github.com/onsi/ginkgo/v2"
21 | gomega "github.com/onsi/gomega"
22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23 | "sigs.k8s.io/controller-runtime/pkg/client"
24 |
25 | testv1 "github.com/bankdata/styra-controller/api/test/v1"
26 | "github.com/bankdata/styra-controller/internal/labels"
27 | )
28 |
29 | var _ = ginkgo.Describe("SetManagedBy", func() {
30 | ginkgo.It("should set the managed-by label", func() {
31 | o := testv1.Object{}
32 | labels.SetManagedBy(&o)
33 | gomega.Ω(o.Labels["app.kubernetes.io/managed-by"]).To(gomega.Equal("styra-controller"))
34 | })
35 | })
36 |
37 | var _ = ginkgo.DescribeTable("HasManagedBy",
38 | func(o client.Object, expected bool) {
39 | gomega.Ω(labels.HasManagedBy(o)).To(gomega.Equal(expected))
40 | },
41 | ginkgo.Entry(
42 | "should return false if labels is nil",
43 | &testv1.Object{},
44 | false,
45 | ),
46 | ginkgo.Entry(
47 | "should return false if label is set to something unexpected",
48 | &testv1.Object{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{
49 | "app.kubernetes.io/managed-by": "something-unexpected",
50 | }}},
51 | false,
52 | ),
53 | ginkgo.Entry(
54 | "should return true if label is set as expected",
55 | &testv1.Object{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{
56 | "app.kubernetes.io/managed-by": "styra-controller",
57 | }}},
58 | true,
59 | ),
60 | )
61 |
62 | var _ = ginkgo.DescribeTable("ControllerClassMatches",
63 | func(o client.Object, class string, expected bool) {
64 | gomega.Ω(labels.ControllerClassMatches(o, class)).To(gomega.Equal(expected))
65 | },
66 | ginkgo.Entry(
67 | "should return false if labels is nil and class is non-empty",
68 | &testv1.Object{},
69 | "test",
70 | false,
71 | ),
72 | ginkgo.Entry(
73 | "should return true if labels is nil and class empty",
74 | &testv1.Object{},
75 | "",
76 | true,
77 | ),
78 | ginkgo.Entry(
79 | "should return true if label is missing but class is empty",
80 | &testv1.Object{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{}}},
81 | "",
82 | true,
83 | ),
84 | ginkgo.Entry(
85 | "should return true if label value matches",
86 | &testv1.Object{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{
87 | "styra-controller/class": "test",
88 | }}},
89 | "test",
90 | true,
91 | ),
92 | )
93 |
--------------------------------------------------------------------------------
/internal/predicate/predicate.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 predicate contains predicates used by the controllers.
18 | package predicate
19 |
20 | import (
21 | "github.com/pkg/errors"
22 | "sigs.k8s.io/controller-runtime/pkg/predicate"
23 |
24 | "github.com/bankdata/styra-controller/internal/labels"
25 | )
26 |
27 | // ControllerClass creates a predicate which ensures that we only reconcile
28 | // resources that match the controller class label selector
29 | // labels.ControllerClassLabelSelector.
30 | func ControllerClass(class string) (predicate.Predicate, error) {
31 | labelSelector := labels.ControllerClassLabelSelector(class)
32 | predicate, err := predicate.LabelSelectorPredicate(labelSelector)
33 | if err != nil {
34 | return nil, errors.Wrap(err, "could not create LabelSelectorPredicate")
35 | }
36 | return predicate, nil
37 | }
38 |
--------------------------------------------------------------------------------
/internal/predicate/predicate_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 predicate_test
18 |
19 | import (
20 | ginkgo "github.com/onsi/ginkgo/v2"
21 | gomega "github.com/onsi/gomega"
22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23 | "sigs.k8s.io/controller-runtime/pkg/client"
24 | "sigs.k8s.io/controller-runtime/pkg/event"
25 |
26 | testv1 "github.com/bankdata/styra-controller/api/test/v1"
27 | "github.com/bankdata/styra-controller/internal/predicate"
28 | )
29 |
30 | var _ = ginkgo.DescribeTable("ControllerClass",
31 | func(class string, obj client.Object, expected bool) {
32 | p, err := predicate.ControllerClass(class)
33 | gomega.Ω(err).NotTo(gomega.HaveOccurred())
34 | gomega.Ω(p.Create(event.CreateEvent{Object: obj})).To(gomega.Equal(expected))
35 | },
36 |
37 | ginkgo.Entry("empty class. no label.", "", &testv1.Object{}, true),
38 |
39 | ginkgo.Entry("empty class. label is set.", "", &testv1.Object{
40 | ObjectMeta: metav1.ObjectMeta{
41 | Labels: map[string]string{"styra-controller/class": "test"},
42 | },
43 | }, false),
44 |
45 | ginkgo.Entry("empty class. label is empty.", "", &testv1.Object{
46 | ObjectMeta: metav1.ObjectMeta{
47 | Labels: map[string]string{"styra-controller/class": ""},
48 | },
49 | }, false),
50 |
51 | ginkgo.Entry("class set. no label.", "test", &testv1.Object{}, false),
52 |
53 | ginkgo.Entry("class set. label mismatch", "test", &testv1.Object{
54 | ObjectMeta: metav1.ObjectMeta{
55 | Labels: map[string]string{"styra-controller/class": "tset"},
56 | },
57 | }, false),
58 |
59 | ginkgo.Entry("class set. label match.", "test", &testv1.Object{
60 | ObjectMeta: metav1.ObjectMeta{
61 | Labels: map[string]string{"styra-controller/class": "test"},
62 | },
63 | }, true),
64 | )
65 |
--------------------------------------------------------------------------------
/internal/predicate/suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 predicate_test
18 |
19 | import (
20 | "testing"
21 |
22 | ginkgo "github.com/onsi/ginkgo/v2"
23 | gomega "github.com/onsi/gomega"
24 | )
25 |
26 | func TestPtr(t *testing.T) {
27 | gomega.RegisterFailHandler(ginkgo.Fail)
28 | ginkgo.RunSpecs(t, "internal/predicate")
29 | }
30 |
--------------------------------------------------------------------------------
/internal/sentry/sentry.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 sentry contains a reconciler middleware which sends errors to
18 | // Sentry.
19 | package sentry
20 |
21 | import (
22 | "context"
23 | "errors"
24 | "strings"
25 |
26 | "github.com/getsentry/sentry-go"
27 | ctrl "sigs.k8s.io/controller-runtime"
28 | "sigs.k8s.io/controller-runtime/pkg/reconcile"
29 |
30 | "github.com/bankdata/styra-controller/pkg/styra"
31 | )
32 |
33 | type sentryReconciler struct {
34 | next reconcile.Reconciler
35 | }
36 |
37 | // Reconcile implements reconciler.Reconcile for the sentry middleware.
38 | func (r *sentryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
39 | result, err := r.next.Reconcile(ctx, req)
40 |
41 | if sentry.CurrentHub().Client() != nil {
42 | if err != nil {
43 | hub := sentry.CurrentHub().Clone()
44 | var styraerror *styra.HTTPError
45 | if errors.As(err, &styraerror) {
46 | hub.ConfigureScope(func(scope *sentry.Scope) {
47 | scope.SetContext("Styra Client", map[string]interface{}{
48 | "body": styraerror.Body,
49 | "statuscode": styraerror.StatusCode,
50 | })
51 | })
52 | }
53 |
54 | hub.ConfigureScope(func(scope *sentry.Scope) {
55 | scope.SetTags(map[string]string{
56 | "namespace": req.Namespace,
57 | "name": req.Name,
58 | })
59 | })
60 | if !isUserError(err.Error()) {
61 | hub.CaptureException(err)
62 | }
63 | }
64 | }
65 | return result, err
66 | }
67 |
68 | // Decorate applies the sentry middleware to the given reconcile.Reconciler.
69 | func Decorate(r reconcile.Reconciler) reconcile.Reconciler {
70 | return &sentryReconciler{next: r}
71 | }
72 |
73 | func isUserError(msg string) bool {
74 | uniqueGitConfig := "the combination of url, branch, commit-sha and path must be unique across all git repos"
75 | couldNotFindCredentialsSecret := "Could not find credentials Secret"
76 |
77 | return strings.Contains(msg, uniqueGitConfig) ||
78 | strings.Contains(msg, couldNotFindCredentialsSecret)
79 | }
80 |
--------------------------------------------------------------------------------
/internal/template/members.tpl:
--------------------------------------------------------------------------------
1 | {{ define "members" }}
2 |
3 | {{ range .Members }}
4 | {{ if not (hiddenMember .)}}
5 |
6 |
7 | {{ fieldName . }}
8 |
9 | {{ if linkForType .Type }}
10 |
11 | {{ typeDisplayName .Type }}
12 |
13 | {{ else }}
14 | {{ typeDisplayName .Type }}
15 | {{ end }}
16 |
17 | |
18 |
19 | {{ if fieldEmbedded . }}
20 |
21 | (Members of {{ fieldName . }} are embedded into this type.)
22 |
23 | {{ end}}
24 |
25 | {{ if isOptionalMember .}}
26 | (Optional)
27 | {{ end }}
28 |
29 | {{ safe (renderComments .CommentLines) }}
30 |
31 | {{ if and (eq (.Type.Name.Name) "ObjectMeta") }}
32 | Refer to the Kubernetes API documentation for the fields of the
33 | metadata field.
34 | {{ end }}
35 |
36 | {{ if or (eq (fieldName .) "spec") }}
37 |
38 |
39 |
40 | {{ template "members" .Type }}
41 |
42 | {{ end }}
43 | |
44 |
45 | {{ end }}
46 | {{ end }}
47 |
48 | {{ end }}
49 |
--------------------------------------------------------------------------------
/internal/template/pkg.tpl:
--------------------------------------------------------------------------------
1 | {{ define "packages" }}
2 |
3 | {{ with .packages}}
4 | Packages:
5 |
12 | {{ end}}
13 |
14 | {{ range .packages }}
15 |
16 | {{- packageDisplayName . -}}
17 |
18 |
19 | {{ with (index .GoPackages 0 )}}
20 | {{ with .DocComments }}
21 |
22 | {{ safe (renderComments .) }}
23 |
24 | {{ end }}
25 | {{ end }}
26 |
27 | Resource Types:
28 |
29 | {{- range (visibleTypes (sortedTypes .Types)) -}}
30 | {{ if isExportedType . -}}
31 | -
32 | {{ typeDisplayName . }}
33 |
34 | {{- end }}
35 | {{- end -}}
36 |
37 |
38 | {{ range (visibleTypes (sortedTypes .Types))}}
39 | {{ template "type" . }}
40 | {{ end }}
41 |
42 | {{ end }}
43 |
44 |
45 | Generated with gen-crd-api-reference-docs
46 | {{ with .gitCommit }} on git commit {{ . }}
{{end}}.
47 |
48 |
49 | {{ end }}
50 |
--------------------------------------------------------------------------------
/internal/template/placeholder.go:
--------------------------------------------------------------------------------
1 | // Package template is a placeholder file to make Go vendor this directory properly.
2 | package template
3 |
--------------------------------------------------------------------------------
/internal/template/type.tpl:
--------------------------------------------------------------------------------
1 | {{ define "type" }}
2 |
3 |
4 | {{- .Name.Name }}
5 | {{ if eq .Kind "Alias" }}({{.Underlying}}
alias){{ end -}}
6 |
7 | {{ with (typeReferences .) }}
8 |
9 | (Appears on:
10 | {{- $prev := "" -}}
11 | {{- range . -}}
12 | {{- if $prev -}}, {{ end -}}
13 | {{- $prev = . -}}
14 | {{ typeDisplayName . }}
15 | {{- end -}}
16 | )
17 |
18 | {{ end }}
19 |
20 |
21 | {{ safe (renderComments .CommentLines) }}
22 |
23 |
24 | {{ with (constantsOfType .) }}
25 |
26 |
27 |
28 | Value |
29 | Description |
30 |
31 |
32 |
33 | {{- range . -}}
34 |
35 | {{- /*
36 | renderComments implicitly creates a element, so we
37 | add one to the display name as well to make the contents
38 | of the two cells align evenly.
39 | */ -}}
40 |
{{ typeDisplayName . }} |
41 | {{ safe (renderComments .CommentLines) }} |
42 |
43 | {{- end -}}
44 |
45 |
46 | {{ end }}
47 |
48 | {{ if .Members }}
49 |
50 |
51 |
52 | Field |
53 | Description |
54 |
55 |
56 |
57 | {{ if isExportedType . }}
58 |
59 |
60 | apiVersion
61 | string |
62 |
63 |
64 | {{apiGroup .}}
65 |
66 | |
67 |
68 |
69 |
70 | kind
71 | string
72 | |
73 | {{.Name.Name}} |
74 |
75 | {{ end }}
76 | {{ template "members" .}}
77 |
78 |
79 | {{ end }}
80 |
81 | {{ end }}
82 |
--------------------------------------------------------------------------------
/internal/webhook/mocks/client.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery. DO NOT EDIT.
2 |
3 | package mocks
4 |
5 | import (
6 | context "context"
7 |
8 | logr "github.com/go-logr/logr"
9 | mock "github.com/stretchr/testify/mock"
10 | )
11 |
12 | // Client is an autogenerated mock type for the Client type
13 | type Client struct {
14 | mock.Mock
15 | }
16 |
17 | // LibraryDatasourceChanged provides a mock function with given fields: _a0, _a1, _a2
18 | func (_m *Client) LibraryDatasourceChanged(_a0 context.Context, _a1 logr.Logger, _a2 string) error {
19 | ret := _m.Called(_a0, _a1, _a2)
20 |
21 | if len(ret) == 0 {
22 | panic("no return value specified for LibraryDatasourceChanged")
23 | }
24 |
25 | var r0 error
26 | if rf, ok := ret.Get(0).(func(context.Context, logr.Logger, string) error); ok {
27 | r0 = rf(_a0, _a1, _a2)
28 | } else {
29 | r0 = ret.Error(0)
30 | }
31 |
32 | return r0
33 | }
34 |
35 | // SystemDatasourceChanged provides a mock function with given fields: _a0, _a1, _a2, _a3
36 | func (_m *Client) SystemDatasourceChanged(_a0 context.Context, _a1 logr.Logger, _a2 string, _a3 string) error {
37 | ret := _m.Called(_a0, _a1, _a2, _a3)
38 |
39 | if len(ret) == 0 {
40 | panic("no return value specified for SystemDatasourceChanged")
41 | }
42 |
43 | var r0 error
44 | if rf, ok := ret.Get(0).(func(context.Context, logr.Logger, string, string) error); ok {
45 | r0 = rf(_a0, _a1, _a2, _a3)
46 | } else {
47 | r0 = ret.Error(0)
48 | }
49 |
50 | return r0
51 | }
52 |
53 | // NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
54 | // The first argument is typically a *testing.T value.
55 | func NewClient(t interface {
56 | mock.TestingT
57 | Cleanup(func())
58 | }) *Client {
59 | mock := &Client{}
60 | mock.Mock.Test(t)
61 |
62 | t.Cleanup(func() { mock.AssertExpectations(t) })
63 |
64 | return mock
65 | }
66 |
--------------------------------------------------------------------------------
/internal/webhook/styra/v1alpha1/library_webhook_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2025.
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 v1alpha1
18 |
19 | import (
20 | ginkgo "github.com/onsi/ginkgo/v2"
21 | gomega "github.com/onsi/gomega"
22 |
23 | styrav1alpha1 "github.com/bankdata/styra-controller/api/styra/v1alpha1"
24 | // TODO (user): Add any additional imports if needed
25 | )
26 |
27 | var _ = ginkgo.Describe("Library Webhook", func() {
28 | var (
29 | obj *styrav1alpha1.Library
30 | oldObj *styrav1alpha1.Library
31 | validator LibraryCustomValidator
32 | defaulter LibraryCustomDefaulter
33 | )
34 |
35 | ginkgo.BeforeEach(func() {
36 | obj = &styrav1alpha1.Library{}
37 | oldObj = &styrav1alpha1.Library{}
38 | validator = LibraryCustomValidator{}
39 | gomega.Expect(validator).NotTo(gomega.BeNil(), "Expected validator to be initialized")
40 | defaulter = LibraryCustomDefaulter{}
41 | gomega.Expect(defaulter).NotTo(gomega.BeNil(), "Expected defaulter to be initialized")
42 | gomega.Expect(oldObj).NotTo(gomega.BeNil(), "Expected oldObj to be initialized")
43 | gomega.Expect(obj).NotTo(gomega.BeNil(), "Expected obj to be initialized")
44 | // TODO (user): Add any setup logic common to all tests
45 | })
46 |
47 | ginkgo.AfterEach(func() {
48 | // TODO (user): Add any teardown logic common to all tests
49 | })
50 |
51 | ginkgo.Context("When creating Library under Defaulting Webhook", func() {
52 | // TODO (user): Add logic for defaulting webhooks
53 | // Example:
54 | // It("Should apply defaults when a required field is empty", func() {
55 | // By("simulating a scenario where defaults should be applied")
56 | // obj.SomeFieldWithDefault = ""
57 | // By("calling the Default method to apply defaults")
58 | // defaulter.Default(ctx, obj)
59 | // By("checking that the default values are set")
60 | // Expect(obj.SomeFieldWithDefault).To(Equal("default_value"))
61 | // })
62 | })
63 |
64 | ginkgo.Context("When creating or updating Library under Validating Webhook", func() {
65 | // TODO (user): Add logic for validating webhooks
66 | // Example:
67 | // It("Should deny creation if a required field is missing", func() {
68 | // By("simulating an invalid creation scenario")
69 | // obj.SomeRequiredField = ""
70 | // Expect(validator.ValidateCreate(ctx, obj)).Error().To(HaveOccurred())
71 | // })
72 | //
73 | // It("Should admit creation if all required fields are present", func() {
74 | // By("simulating an invalid creation scenario")
75 | // obj.SomeRequiredField = "valid_value"
76 | // Expect(validator.ValidateCreate(ctx, obj)).To(BeNil())
77 | // })
78 | //
79 | // It("Should validate updates correctly", func() {
80 | // By("simulating a valid update scenario")
81 | // oldObj.SomeRequiredField = "updated_value"
82 | // obj.SomeRequiredField = "updated_value"
83 | // Expect(validator.ValidateUpdate(ctx, oldObj, obj)).To(BeNil())
84 | // })
85 | })
86 |
87 | })
88 |
--------------------------------------------------------------------------------
/internal/webhook/styra/v1beta1/webhook_suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 v1beta1
18 |
19 | import (
20 | "context"
21 | "crypto/tls"
22 | "fmt"
23 | "net"
24 | "path/filepath"
25 | "testing"
26 | "time"
27 |
28 | ginkgo "github.com/onsi/ginkgo/v2"
29 | gomega "github.com/onsi/gomega"
30 |
31 | "k8s.io/apimachinery/pkg/runtime"
32 | ctrl "sigs.k8s.io/controller-runtime"
33 | "sigs.k8s.io/controller-runtime/pkg/client"
34 | "sigs.k8s.io/controller-runtime/pkg/envtest"
35 | logf "sigs.k8s.io/controller-runtime/pkg/log"
36 | "sigs.k8s.io/controller-runtime/pkg/log/zap"
37 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
38 | "sigs.k8s.io/controller-runtime/pkg/webhook"
39 |
40 | //+kubebuilder:scaffold:imports
41 | "github.com/bankdata/styra-controller/api/styra/v1alpha1"
42 | "github.com/bankdata/styra-controller/api/styra/v1beta1"
43 | )
44 |
45 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to
46 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
47 |
48 | var (
49 | k8sClient client.Client
50 | testEnv *envtest.Environment
51 | ctx context.Context
52 | cancel context.CancelFunc
53 | )
54 |
55 | func TestAPIs(t *testing.T) {
56 | gomega.RegisterFailHandler(ginkgo.Fail)
57 |
58 | ginkgo.RunSpecs(t, "test/integration/webhook")
59 | }
60 |
61 | var _ = ginkgo.BeforeSuite(func() {
62 | logf.SetLogger(zap.New(zap.WriteTo(ginkgo.GinkgoWriter), zap.UseDevMode(true)))
63 |
64 | if !ginkgo.Label("integration").MatchesLabelFilter(ginkgo.GinkgoLabelFilter()) {
65 | return
66 | }
67 |
68 | scheme := runtime.NewScheme()
69 | err := v1alpha1.AddToScheme(scheme)
70 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
71 | err = v1beta1.AddToScheme(scheme)
72 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
73 |
74 | //+kubebuilder:scaffold:scheme
75 |
76 | ginkgo.By("bootstrapping test environment")
77 | testEnv = &envtest.Environment{
78 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "config", "crd", "bases")},
79 | ErrorIfCRDPathMissing: false,
80 | WebhookInstallOptions: envtest.WebhookInstallOptions{
81 | Paths: []string{filepath.Join("..", "..", "..", "..", "config", "webhook")},
82 | },
83 | CRDInstallOptions: envtest.CRDInstallOptions{
84 | Scheme: scheme,
85 | },
86 | }
87 |
88 | cfg, err := testEnv.Start()
89 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
90 | gomega.Expect(cfg).NotTo(gomega.BeNil())
91 |
92 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
93 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
94 | gomega.Expect(k8sClient).NotTo(gomega.BeNil())
95 |
96 | // start webhook server using Manager
97 | webhookInstallOptions := &testEnv.WebhookInstallOptions
98 | mgr, err := ctrl.NewManager(cfg, ctrl.Options{
99 | Scheme: scheme,
100 | WebhookServer: webhook.NewServer(webhook.Options{
101 | Host: webhookInstallOptions.LocalServingHost,
102 | Port: webhookInstallOptions.LocalServingPort,
103 | CertDir: webhookInstallOptions.LocalServingCertDir,
104 | }),
105 | LeaderElection: false,
106 | Metrics: metricsserver.Options{
107 | BindAddress: "0",
108 | },
109 | })
110 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
111 |
112 | err = SetupSystemWebhookWithManager(mgr)
113 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
114 |
115 | //+kubebuilder:scaffold:webhook
116 |
117 | ctx, cancel = context.WithCancel(context.Background())
118 |
119 | go func() {
120 | defer ginkgo.GinkgoRecover()
121 | err = mgr.Start(ctx)
122 | if err != nil {
123 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
124 | }
125 | }()
126 |
127 | // wait for the webhook server to get ready
128 | dialer := &net.Dialer{Timeout: time.Second}
129 | addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)
130 | gomega.Eventually(func() error {
131 | conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true})
132 | if err != nil {
133 | return err
134 | }
135 | err = conn.Close()
136 | if err != nil {
137 | return err
138 | }
139 | return nil
140 | }).Should(gomega.Succeed())
141 |
142 | })
143 |
144 | var _ = ginkgo.AfterSuite(func() {
145 | if testing.Short() {
146 | return
147 | }
148 |
149 | if cancel != nil {
150 | cancel()
151 | }
152 |
153 | ginkgo.By("tearing down the test environment")
154 | if testEnv != nil {
155 | err := testEnv.Stop()
156 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
157 | }
158 | })
159 |
--------------------------------------------------------------------------------
/internal/webhook/suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 webhook
18 |
19 | import (
20 | "testing"
21 |
22 | ginkgo "github.com/onsi/ginkgo/v2"
23 | gomega "github.com/onsi/gomega"
24 | )
25 |
26 | func TestWebhookClient(t *testing.T) {
27 |
28 | gomega.RegisterFailHandler(ginkgo.Fail)
29 | ginkgo.RunSpecs(t, "internal/webhook")
30 | }
31 |
--------------------------------------------------------------------------------
/internal/webhook/webhook.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 webhook contains helpers for the notifaction webhooks of the
18 | // controller. These webhooks can be used to notify other systems when
19 | // something happens in the controller.
20 | package webhook
21 |
22 | import (
23 | "bytes"
24 | "context"
25 | "encoding/json"
26 | "io"
27 | "net/http"
28 |
29 | "github.com/go-logr/logr"
30 | "github.com/pkg/errors"
31 | )
32 |
33 | // Client defines the interface for the notification webhook client.
34 | type Client interface {
35 | SystemDatasourceChanged(context.Context, logr.Logger, string, string) error
36 | LibraryDatasourceChanged(context.Context, logr.Logger, string) error
37 | }
38 |
39 | type client struct {
40 | hc http.Client
41 | libraryDatasourceChanged string
42 | systemDatasourceChanged string
43 | }
44 |
45 | // New creates a new webhook notification Client.
46 | func New(systemDatasourceChanged string, libraryDatasourceChanged string) Client {
47 | return &client{
48 | hc: http.Client{},
49 | systemDatasourceChanged: systemDatasourceChanged,
50 | libraryDatasourceChanged: libraryDatasourceChanged,
51 | }
52 | }
53 |
54 | func (client *client) LibraryDatasourceChanged(ctx context.Context, log logr.Logger, datasourceID string) error {
55 | if client.libraryDatasourceChanged == "" {
56 | return errors.New("libraryDatasourceChanged webhook not configured")
57 | }
58 |
59 | body := map[string]string{"datasourceID": datasourceID}
60 | jsonData, err := json.Marshal(body)
61 |
62 | if err != nil {
63 | log.Error(err, "Failed to marshal request body")
64 | return errors.Wrap(err, "Failed to marshal request body")
65 | }
66 |
67 | r, err := http.NewRequestWithContext(ctx, http.MethodPost, client.libraryDatasourceChanged, bytes.NewBuffer(jsonData))
68 | if err != nil {
69 | log.Error(err, "Failed to create request to webhook")
70 | return errors.Wrap(err, "Failed to create request to webhook")
71 | }
72 | r.Header.Set("Content-Type", "application/json")
73 |
74 | resp, err := client.hc.Do(r)
75 | if err != nil {
76 | log.Error(err, "Failed in call to webhook")
77 | return errors.Wrap(err, "Failed in call to webhook")
78 | }
79 |
80 | if resp.StatusCode < 200 || resp.StatusCode > 299 {
81 | log.Info("Response status code is not 2XX")
82 | bodyBytes, err := io.ReadAll(resp.Body)
83 | if err != nil {
84 | log.Error(err, "Could not read response body")
85 | return errors.Errorf("Could not read response body")
86 | }
87 | bodyString := string(bodyBytes)
88 | return errors.Errorf("response status code is %d, response body is %s", resp.StatusCode, bodyString)
89 | }
90 |
91 | log.Info("Called library webhook successfully")
92 | return nil
93 | }
94 |
95 | // DatasourceChanged notifies the webhook that a datasource has changed.
96 | func (client *client) SystemDatasourceChanged(
97 | ctx context.Context,
98 | log logr.Logger,
99 | systemID string,
100 | dsID string) error {
101 | if client.systemDatasourceChanged == "" {
102 | return errors.New("systemDatasourceChanged webhook not configured")
103 | }
104 |
105 | body := map[string]string{"systemId": systemID, "datasourceId": dsID}
106 | jsonData, err := json.Marshal(body)
107 |
108 | if err != nil {
109 | log.Error(err, "Failed to marshal request body")
110 | return errors.Wrap(err, "Failed to marshal request body")
111 | }
112 |
113 | r, err := http.NewRequestWithContext(ctx, http.MethodPost, client.systemDatasourceChanged, bytes.NewBuffer(jsonData))
114 | if err != nil {
115 | log.Error(err, "Failed to create request to webhook")
116 | return errors.Wrap(err, "Failed to create request to webhook")
117 | }
118 |
119 | r.Header.Set("Content-Type", "application/json")
120 |
121 | resp, err := client.hc.Do(r)
122 |
123 | if err != nil {
124 | log.Error(err, "Failed in call to webhook")
125 | return errors.Wrap(err, "Failed in call to webhook")
126 | }
127 |
128 | if resp.StatusCode < 200 || resp.StatusCode > 299 {
129 | log.Info("Response status code is not 2XX")
130 | bodyBytes, err := io.ReadAll(resp.Body)
131 | if err != nil {
132 | log.Error(err, "Could not read response body")
133 | return errors.Errorf("Could not read response body")
134 | }
135 | bodyString := string(bodyBytes)
136 | return errors.Errorf("response status code is %d, response body is %s", resp.StatusCode, bodyString)
137 | }
138 |
139 | log.Info("Called system webhook successfully")
140 | return nil
141 | }
142 |
--------------------------------------------------------------------------------
/pkg/ptr/ptr.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 ptr contains helpers for creating pointers to built-in types.
18 | package ptr
19 |
20 | // Bool creates a pointer to a bool.
21 | func Bool(b bool) *bool {
22 | return &b
23 | }
24 |
25 | // String creates a pointer to a string.
26 | func String(s string) *string {
27 | return &s
28 | }
29 |
30 | // Int creates a pointer to an int.
31 | func Int(i int) *int {
32 | return &i
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/ptr/ptr_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 ptr_test
18 |
19 | import (
20 | ginkgo "github.com/onsi/ginkgo/v2"
21 | gomega "github.com/onsi/gomega"
22 |
23 | "github.com/bankdata/styra-controller/pkg/ptr"
24 | )
25 |
26 | var _ = ginkgo.Describe("Bool", func() {
27 | ginkgo.It("should return a pointer to the boolean", func() {
28 | gomega.Expect(*ptr.Bool(true)).To(gomega.BeTrue())
29 | gomega.Expect(*ptr.Bool(false)).To(gomega.BeFalse())
30 | })
31 | })
32 |
33 | var _ = ginkgo.Describe("String", func() {
34 | ginkgo.It("should return a pointer to the string", func() {
35 | gomega.Expect(*ptr.String("")).To(gomega.Equal(""))
36 | gomega.Expect(*ptr.String("test")).To(gomega.Equal("test"))
37 | })
38 | })
39 |
40 | var _ = ginkgo.Describe("Int", func() {
41 | ginkgo.It("should return a pointer to the int", func() {
42 | gomega.Expect(*ptr.Int(0)).To(gomega.Equal(0))
43 | gomega.Expect(*ptr.Int(42)).To(gomega.Equal(42))
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/pkg/ptr/suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 ptr_test
18 |
19 | import (
20 | "testing"
21 |
22 | ginkgo "github.com/onsi/ginkgo/v2"
23 | gomega "github.com/onsi/gomega"
24 | )
25 |
26 | func TestPtr(t *testing.T) {
27 | gomega.RegisterFailHandler(ginkgo.Fail)
28 | ginkgo.RunSpecs(t, "pkg/ptr")
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/styra/client.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra
18 |
19 | import (
20 | "bytes"
21 | "context"
22 | "encoding/json"
23 | "fmt"
24 | "net/http"
25 | "time"
26 |
27 | "github.com/patrickmn/go-cache"
28 | "github.com/pkg/errors"
29 | )
30 |
31 | // ClientInterface defines the interface for the Styra client.
32 | type ClientInterface interface {
33 | GetSystem(ctx context.Context, id string) (*GetSystemResponse, error)
34 | GetSystemByName(ctx context.Context, name string) (*GetSystemResponse, error)
35 |
36 | CreateUpdateSecret(
37 | ctx context.Context,
38 | secretID string,
39 | request *CreateUpdateSecretsRequest,
40 | ) (*CreateUpdateSecretResponse, error)
41 | DeleteSecret(
42 | ctx context.Context,
43 | secretID string,
44 | ) (*DeleteSecretResponse, error)
45 |
46 | GetUser(ctx context.Context, name string) (*GetUserResponse, error)
47 | GetUsers(ctx context.Context) (*GetUsersResponse, bool, error)
48 | InvalidateCache()
49 |
50 | CreateInvitation(ctx context.Context, email bool, name string) (*CreateInvitationResponse, error)
51 |
52 | ListRoleBindingsV2(ctx context.Context, params *ListRoleBindingsV2Params) (*ListRoleBindingsV2Response, error)
53 |
54 | CreateRoleBinding(ctx context.Context, request *CreateRoleBindingRequest) (*CreateRoleBindingResponse, error)
55 |
56 | UpdateRoleBindingSubjects(
57 | ctx context.Context,
58 | id string,
59 | request *UpdateRoleBindingSubjectsRequest,
60 | ) (*UpdateRoleBindingSubjectsResponse, error)
61 |
62 | DeleteRoleBindingV2(ctx context.Context, id string) (*DeleteRoleBindingV2Response, error)
63 |
64 | GetDatasource(ctx context.Context, id string) (*GetDatasourceResponse, error)
65 |
66 | UpsertDatasource(
67 | ctx context.Context,
68 | id string,
69 | request *UpsertDatasourceRequest,
70 | ) (*UpsertDatasourceResponse, error)
71 |
72 | DeleteDatasource(ctx context.Context, id string) (*DeleteDatasourceResponse, error)
73 |
74 | GetLibrary(ctx context.Context, id string) (*GetLibraryResponse, error)
75 | UpsertLibrary(ctx context.Context, id string, request *UpsertLibraryRequest) (*UpsertLibraryResponse, error)
76 |
77 | UpdateSystem(ctx context.Context, id string, request *UpdateSystemRequest) (*UpdateSystemResponse, error)
78 |
79 | DeleteSystem(ctx context.Context, id string) (*DeleteSystemResponse, error)
80 |
81 | CreateSystem(ctx context.Context, request *CreateSystemRequest) (*CreateSystemResponse, error)
82 |
83 | PutSystem(context.Context, *PutSystemRequest, string, map[string]string) (*PutSystemResponse, error)
84 |
85 | GetOPAConfig(ctx context.Context, systemID string) (OPAConfig, error)
86 |
87 | VerifyGitConfiguration(ctx context.Context, request *VerfiyGitConfigRequest) (*VerfiyGitConfigResponse, error)
88 |
89 | DeletePolicy(ctx context.Context, policyName string) (*DeletePolicyResponse, error)
90 |
91 | UpdateWorkspace(ctx context.Context, request *UpdateWorkspaceRequest) (*UpdateWorkspaceResponse, error)
92 | UpdateWorkspaceRaw(ctx context.Context, request interface{}) (*UpdateWorkspaceResponse, error)
93 | }
94 |
95 | // Client is a client for the Styra APIs.
96 | type Client struct {
97 | HTTPClient http.Client
98 | URL string
99 | token string
100 | Cache *cache.Cache
101 | }
102 |
103 | // New creates a new Styra ClientInterface.
104 | func New(url string, token string) ClientInterface {
105 | c := cache.New(1*time.Hour, 10*time.Minute)
106 |
107 | return &Client{
108 | URL: url,
109 | HTTPClient: http.Client{},
110 | token: token,
111 | Cache: c,
112 | }
113 | }
114 |
115 | // InvalidateCache invalidates the entire cache
116 | func (c *Client) InvalidateCache() {
117 | c.Cache.Flush()
118 | }
119 |
120 | func (c *Client) newRequest(
121 | ctx context.Context,
122 | method string,
123 | endpoint string,
124 | body interface{},
125 | headers map[string]string,
126 | ) (*http.Request, error) {
127 | u := fmt.Sprintf("%s%s", c.URL, endpoint)
128 |
129 | var b bytes.Buffer
130 | if body != nil {
131 | if err := json.NewEncoder(&b).Encode(body); err != nil {
132 | return nil, errors.Wrap(err, "could not encode body")
133 | }
134 | }
135 |
136 | r, err := http.NewRequestWithContext(ctx, method, u, &b)
137 | if err != nil {
138 | return nil, errors.Wrap(err, "could not create request")
139 | }
140 |
141 | r.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))
142 | r.Header.Set("Content-Type", "application/json")
143 |
144 | for k, v := range headers {
145 | r.Header.Set(k, v)
146 | }
147 |
148 | return r, nil
149 | }
150 |
151 | func (c *Client) request(
152 | ctx context.Context,
153 | method string,
154 | endpoint string,
155 | body interface{},
156 | headers map[string]string,
157 | ) (*http.Response, error) {
158 | req, err := c.newRequest(ctx, method, endpoint, body, headers)
159 | if err != nil {
160 | return nil, err
161 | }
162 |
163 | res, err := c.HTTPClient.Do(req)
164 | if err != nil {
165 | return nil, errors.Wrap(err, "could not send request")
166 | }
167 |
168 | return res, nil
169 | }
170 |
--------------------------------------------------------------------------------
/pkg/styra/client_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra_test
18 |
19 | import (
20 | "net/http"
21 |
22 | "github.com/bankdata/styra-controller/pkg/styra"
23 | "github.com/patrickmn/go-cache"
24 | )
25 |
26 | type roundTripFunc func(req *http.Request) *http.Response
27 |
28 | func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
29 | return f(req), nil
30 | }
31 |
32 | func newTestClient(f roundTripFunc) styra.ClientInterface {
33 | return &styra.Client{
34 | URL: "http://test.com",
35 | HTTPClient: http.Client{
36 | Transport: roundTripFunc(f),
37 | },
38 | }
39 | }
40 |
41 | func newTestClientWithCache(f roundTripFunc, cache *cache.Cache) styra.ClientInterface {
42 | return &styra.Client{
43 | URL: "http://test.com",
44 | HTTPClient: http.Client{
45 | Transport: roundTripFunc(f),
46 | },
47 | Cache: cache,
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/styra/http_error.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra
18 |
19 | import (
20 | "encoding/json"
21 | "fmt"
22 |
23 | "github.com/pkg/errors"
24 | )
25 |
26 | // HTTPError represents an error that occurred when interacting with the Styra
27 | // API.
28 | type HTTPError struct {
29 | StatusCode int
30 | Body string
31 | }
32 |
33 | // Error implements the error interface.
34 | func (styraerror *HTTPError) Error() string {
35 | return fmt.Sprintf("styra: unexpected statuscode: %d, body: %s", styraerror.StatusCode, styraerror.Body)
36 | }
37 |
38 | // NewHTTPError creates a new HTTPError based on the statuscode and body from a
39 | // failed call to the Styra API.
40 | func NewHTTPError(statuscode int, body string) error {
41 | styraerror := &HTTPError{
42 | StatusCode: statuscode,
43 | }
44 |
45 | if isValidJSON(body) {
46 | styraerror.Body = body
47 | } else {
48 | styraerror.Body = "invalid JSON response"
49 | }
50 |
51 | return errors.WithStack(styraerror)
52 | }
53 |
54 | func isValidJSON(data string) bool {
55 | var out interface{}
56 | return json.Unmarshal([]byte(data), &out) == nil
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/styra/invitations.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "io"
23 | "net/http"
24 | "strconv"
25 |
26 | "github.com/pkg/errors"
27 | )
28 |
29 | const (
30 | endpointV1Invitations = "/v1/invitations"
31 | )
32 |
33 | // CreateInvitationResponse is the response type for calls to the
34 | // POST /v1/invitations endpoint in the Styra API.
35 | type CreateInvitationResponse struct {
36 | StatusCode int
37 | Body []byte
38 | }
39 |
40 | // CreateInvitationRequest is the request body for the
41 | // POST /v1/invitations endpoint in the Styra API.
42 | type CreateInvitationRequest struct {
43 | UserID string `json:"user_id"`
44 | }
45 |
46 | // CreateInvitation calls the POST /v1/invitations endpoint in the Styra API.
47 | func (c *Client) CreateInvitation(ctx context.Context, email bool, name string) (*CreateInvitationResponse, error) {
48 | createInvitationData := CreateInvitationRequest{
49 | UserID: name,
50 | }
51 |
52 | res, err := c.request(
53 | ctx,
54 | http.MethodPost,
55 | fmt.Sprintf("%s?email=%s", endpointV1Invitations, strconv.FormatBool(email)),
56 | createInvitationData,
57 | nil,
58 | )
59 | if err != nil {
60 | return nil, err
61 | }
62 |
63 | body, err := io.ReadAll(res.Body)
64 | if err != nil {
65 | return nil, errors.Wrap(err, "could not read body")
66 | }
67 |
68 | if res.StatusCode != http.StatusOK {
69 | err := NewHTTPError(res.StatusCode, string(body))
70 | return nil, err
71 | }
72 |
73 | r := CreateInvitationResponse{
74 | StatusCode: res.StatusCode,
75 | Body: body,
76 | }
77 |
78 | return &r, nil
79 | }
80 |
--------------------------------------------------------------------------------
/pkg/styra/invitations_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra_test
18 |
19 | import (
20 | "bytes"
21 | "context"
22 | "encoding/json"
23 | "errors"
24 | "io"
25 | "net/http"
26 | "strconv"
27 |
28 | ginkgo "github.com/onsi/ginkgo/v2"
29 | gomega "github.com/onsi/gomega"
30 |
31 | "github.com/bankdata/styra-controller/pkg/styra"
32 | )
33 |
34 | var _ = ginkgo.Describe("CreateInvitation", func() {
35 |
36 | type test struct {
37 | email bool
38 | name string
39 | responseCode int
40 | responseBody string
41 | createInvitationRequest *styra.CreateInvitationRequest
42 | expectStyraErr bool
43 | }
44 |
45 | ginkgo.DescribeTable("CreateInvitation", func(test test) {
46 | c := newTestClient(func(r *http.Request) *http.Response {
47 | bs, err := io.ReadAll(r.Body)
48 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
49 | var b bytes.Buffer
50 | gomega.Expect(json.NewEncoder(&b).Encode(test.createInvitationRequest)).To(gomega.Succeed())
51 | gomega.Expect(bs).To(gomega.Equal(b.Bytes()))
52 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodPost))
53 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/invitations?email=" +
54 | strconv.FormatBool(test.email)))
55 |
56 | return &http.Response{
57 | Header: make(http.Header),
58 | StatusCode: test.responseCode,
59 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)),
60 | }
61 | })
62 |
63 | res, err := c.CreateInvitation(context.Background(), test.email, test.name)
64 | if test.expectStyraErr {
65 | gomega.Expect(res).To(gomega.BeNil())
66 | target := &styra.HTTPError{}
67 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue())
68 | } else {
69 | gomega.Expect(err).ToNot(gomega.HaveOccurred())
70 | gomega.Expect(res.StatusCode).To(gomega.Equal(test.responseCode))
71 | }
72 | },
73 |
74 | ginkgo.Entry("something", test{
75 | name: "name",
76 | responseCode: http.StatusOK,
77 | responseBody: `{
78 | "request_id": "id",
79 | "result": {
80 | "url": "url"
81 | }
82 | }`,
83 | createInvitationRequest: &styra.CreateInvitationRequest{
84 | UserID: "name",
85 | },
86 | }),
87 |
88 | ginkgo.Entry("styra http error", test{
89 | name: "name",
90 | createInvitationRequest: &styra.CreateInvitationRequest{
91 | UserID: "name",
92 | },
93 | responseCode: http.StatusInternalServerError,
94 | expectStyraErr: true,
95 | }),
96 | )
97 | })
98 |
--------------------------------------------------------------------------------
/pkg/styra/library.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra
18 |
19 | import (
20 | "context"
21 | "encoding/json"
22 | "fmt"
23 | "io"
24 | "net/http"
25 |
26 | "github.com/pkg/errors"
27 | )
28 |
29 | const (
30 | endpointV1Libraries = "/v1/libraries"
31 | )
32 |
33 | type getLibraryJSONResponse struct {
34 | Result *LibraryEntityExpanded `json:"result"`
35 | }
36 |
37 | // GetLibraryResponse is the response type for calls to the
38 | // GET /v1/libraries/{id} endpoint in the Styra API.
39 | type GetLibraryResponse struct {
40 | Statuscode int
41 | Body []byte
42 | LibraryEntityExpanded *LibraryEntityExpanded
43 | }
44 |
45 | // LibraryEntityExpanded is the type that defines of a Library
46 | type LibraryEntityExpanded struct {
47 | DataSources []LibraryDatasourceConfig `json:"datasources"`
48 | Description string `json:"description"`
49 | ID string `json:"id"`
50 | ReadOnly bool `json:"read_only"`
51 | SourceControl *LibrarySourceControlConfig `json:"source_control"`
52 | }
53 |
54 | // LibraryDatasourceConfig defines metadata of a datasource
55 | type LibraryDatasourceConfig struct {
56 | Category string `json:"category"`
57 | ID string `json:"id"`
58 | }
59 |
60 | // LibrarySourceControlConfig is a struct from styra where we only use a single field
61 | // but kept for clarity when comparing to the API
62 | type LibrarySourceControlConfig struct {
63 | LibraryOrigin *LibraryGitRepoConfig `json:"library_origin"`
64 | }
65 |
66 | // LibraryGitRepoConfig defines the Git configurations a library can be defined by
67 | type LibraryGitRepoConfig struct {
68 | Commit string `json:"commit"`
69 | Credentials string `json:"credentials"`
70 | Path string `json:"path"`
71 | Reference string `json:"reference"`
72 | URL string `json:"url"`
73 | }
74 |
75 | // UpsertLibraryRequest is the request body for the
76 | // PUT /v1/libraries/{id} endpoint in the Styra API.
77 | type UpsertLibraryRequest struct {
78 | Description string `json:"description"`
79 | ReadOnly bool `json:"read_only"`
80 | SourceControl *LibrarySourceControlConfig `json:"source_control"`
81 | }
82 |
83 | // UpsertLibraryResponse is the response body for the
84 | // PUT /v1/libraries/{id} endpoint in the Styra API.
85 | type UpsertLibraryResponse struct {
86 | StatusCode int
87 | Body []byte
88 | }
89 |
90 | // GetLibrary calls the GET /v1/libraries/{id} endpoint in the
91 | // Styra API.
92 | func (c *Client) GetLibrary(ctx context.Context, id string) (*GetLibraryResponse, error) {
93 | res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("%s/%s", endpointV1Libraries, id), nil, nil)
94 | if err != nil {
95 | return nil, err
96 | }
97 |
98 | body, err := io.ReadAll(res.Body)
99 | if err != nil {
100 | return nil, errors.Wrap(err, "could not read body")
101 | }
102 |
103 | if res.StatusCode != http.StatusOK {
104 | err := NewHTTPError(res.StatusCode, string(body))
105 | return nil, err
106 | }
107 |
108 | var jsonRes getLibraryJSONResponse
109 | if err := json.Unmarshal(body, &jsonRes); err != nil {
110 | return nil, errors.Wrap(err, "could not unmarshal body")
111 | }
112 |
113 | return &GetLibraryResponse{
114 | Statuscode: res.StatusCode,
115 | Body: body,
116 | LibraryEntityExpanded: jsonRes.Result,
117 | }, nil
118 | }
119 |
120 | // UpsertLibrary calls the PUT /v1/libraries/{id} endpoint in the
121 | // Styra API.
122 | func (c *Client) UpsertLibrary(ctx context.Context, id string, request *UpsertLibraryRequest,
123 | ) (*UpsertLibraryResponse, error) {
124 | res, err := c.request(ctx, http.MethodPut, fmt.Sprintf("%s/%s", endpointV1Libraries, id), request, nil)
125 | if err != nil {
126 | return nil, err
127 | }
128 |
129 | body, err := io.ReadAll(res.Body)
130 | if err != nil {
131 | return nil, errors.Wrap(err, "could not read body")
132 | }
133 |
134 | if res.StatusCode != http.StatusOK {
135 | err := NewHTTPError(res.StatusCode, string(body))
136 | return nil, err
137 | }
138 |
139 | resp := UpsertLibraryResponse{
140 | StatusCode: res.StatusCode,
141 | Body: body,
142 | }
143 |
144 | return &resp, nil
145 | }
146 |
--------------------------------------------------------------------------------
/pkg/styra/opaconfig.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "io"
23 | "net/http"
24 |
25 | "github.com/pkg/errors"
26 | "gopkg.in/yaml.v2"
27 | )
28 |
29 | // OPAConfig stores the information retrieved from calling the GET
30 | // /v1/systems/{systemId}/assets/opa-config endpoint in the Styra API.
31 | type OPAConfig struct {
32 | HostURL string
33 | Token string
34 | SystemID string
35 | SystemType string
36 | }
37 |
38 | type getOPAConfigResponse struct {
39 | Discovery getOPAConfigDiscovery `yaml:"discovery"`
40 | Labels getOPAConfigLabels `yaml:"labels"`
41 | Services []getOPAConfigService `yaml:"services"`
42 | }
43 |
44 | type getOPAConfigDiscovery struct {
45 | Name string `yaml:"name"`
46 | Prefix string `yaml:"prefix"`
47 | Service string `yaml:"service"`
48 | }
49 |
50 | type getOPAConfigLabels struct {
51 | SystemID string `yaml:"system-id"`
52 | SystemType string `yaml:"system-type"`
53 | }
54 |
55 | type getOPAConfigService struct {
56 | Credentials getOPAConfigServiceCredentials `yaml:"credentials"`
57 | URL string `yaml:"url"`
58 | }
59 |
60 | type getOPAConfigServiceCredentials struct {
61 | Bearer getOPAConfigServiceBearerCredentials `yaml:"bearer"`
62 | }
63 |
64 | type getOPAConfigServiceBearerCredentials struct {
65 | Token string `yaml:"token"`
66 | }
67 |
68 | // GetOPAConfig calls the GET /v1/systems/{systemId}/assets/opa-config endpoint
69 | // in the Styra API.
70 | func (c *Client) GetOPAConfig(ctx context.Context, systemID string) (OPAConfig, error) {
71 | res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/v1/systems/%s/assets/opa-config", systemID), nil, nil)
72 | if err != nil {
73 | return OPAConfig{}, errors.Wrap(err, "could not get opaconf file")
74 | }
75 |
76 | if res.StatusCode != http.StatusOK {
77 | body, err := io.ReadAll(res.Body)
78 | if err != nil {
79 | return OPAConfig{}, errors.Wrap(err, "could not read body")
80 | }
81 |
82 | err = NewHTTPError(res.StatusCode, string(body))
83 | return OPAConfig{}, err
84 | }
85 |
86 | var getOPAConfigResponse getOPAConfigResponse
87 | if err := yaml.NewDecoder(res.Body).Decode(&getOPAConfigResponse); err != nil {
88 | return OPAConfig{}, errors.Wrap(err, "could not decode opa-config asset response")
89 | }
90 |
91 | if getOPAConfigResponse.Services == nil {
92 | return OPAConfig{}, errors.Errorf("No services in opa config")
93 | }
94 |
95 | opaConfig := OPAConfig{
96 | HostURL: getOPAConfigResponse.Services[0].URL,
97 | Token: getOPAConfigResponse.Services[0].Credentials.Bearer.Token,
98 | SystemID: getOPAConfigResponse.Labels.SystemID,
99 | SystemType: getOPAConfigResponse.Labels.SystemType,
100 | }
101 |
102 | return opaConfig, nil
103 | }
104 |
--------------------------------------------------------------------------------
/pkg/styra/opaconfig_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra_test
18 |
19 | import (
20 | "bytes"
21 | "context"
22 | "errors"
23 | "io"
24 | "net/http"
25 |
26 | ginkgo "github.com/onsi/ginkgo/v2"
27 | gomega "github.com/onsi/gomega"
28 |
29 | "github.com/bankdata/styra-controller/pkg/styra"
30 | )
31 |
32 | var _ = ginkgo.Describe("GetOPAConfig", func() {
33 |
34 | type test struct {
35 | responseBody string
36 | responseCode int
37 | expectedOPAConf styra.OPAConfig
38 | expectStyraErr bool
39 | }
40 |
41 | ginkgo.DescribeTable("GetOPAConfig", func(test test) {
42 | c := newTestClient(func(r *http.Request) *http.Response {
43 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/systems/test_id/assets/opa-config"))
44 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodGet))
45 | return &http.Response{
46 | Header: make(http.Header),
47 | StatusCode: test.responseCode,
48 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)),
49 | }
50 | })
51 |
52 | opaconf, err := c.GetOPAConfig(context.Background(), "test_id")
53 | if test.expectStyraErr {
54 | gomega.Expect(opaconf).To(gomega.Equal(styra.OPAConfig{}))
55 | target := &styra.HTTPError{}
56 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue())
57 | } else {
58 | gomega.Expect(err).ToNot(gomega.HaveOccurred())
59 | gomega.Expect(opaconf).To(gomega.Equal(test.expectedOPAConf))
60 | }
61 | },
62 |
63 | ginkgo.Entry("success", test{
64 | responseBody: `
65 | discovery:
66 | name: discovery-123
67 | prefix: prefix-123
68 | service: service-123
69 | labels:
70 | system-id: system-123
71 | system-type: custom-123
72 | services:
73 | - credentials:
74 | bearer:
75 | token: opa-token-123
76 | url: styra-url-123
77 | - credentials:
78 | bearer:
79 | token: opa-token-1234
80 | url: styra-url-1234`,
81 | expectedOPAConf: styra.OPAConfig{
82 | HostURL: "styra-url-123",
83 | Token: "opa-token-123",
84 | SystemID: "system-123",
85 | SystemType: "custom-123",
86 | },
87 | responseCode: http.StatusOK,
88 | }),
89 | ginkgo.Entry("styra http error", test{
90 | responseCode: http.StatusInternalServerError,
91 | expectStyraErr: true,
92 | }),
93 | )
94 | })
95 |
--------------------------------------------------------------------------------
/pkg/styra/policies.go:
--------------------------------------------------------------------------------
1 | package styra
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "net/http"
8 |
9 | "github.com/pkg/errors"
10 | )
11 |
12 | // DeletePolicyResponse is the response type for calls to
13 | // the DELETE /v1/policies/{policy} endpoint in the Styra API.
14 | type DeletePolicyResponse struct {
15 | StatusCode int
16 | Body []byte
17 | }
18 |
19 | // DeletePolicy calls the DELETE /v1/policies/{policy} endpoint in the Styra API.
20 | func (c *Client) DeletePolicy(ctx context.Context, policyName string) (*DeletePolicyResponse, error) {
21 | res, err := c.request(ctx, http.MethodDelete, fmt.Sprintf("/v1/policies/%s", policyName), nil, nil)
22 | if err != nil {
23 | return nil, errors.Wrap(err, fmt.Sprintf("could not delete policy: %s", policyName))
24 | }
25 |
26 | body, err := io.ReadAll(res.Body)
27 | if err != nil {
28 | return nil, errors.Wrap(err, "failed to read response body")
29 | }
30 |
31 | if res.StatusCode != http.StatusNotFound && res.StatusCode != http.StatusOK {
32 | err := NewHTTPError(res.StatusCode, string(body))
33 | return nil, err
34 | }
35 |
36 | r := DeletePolicyResponse{
37 | StatusCode: res.StatusCode,
38 | Body: body,
39 | }
40 |
41 | return &r, nil
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/styra/policies_test.go:
--------------------------------------------------------------------------------
1 | package styra_test
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "errors"
7 | "io"
8 | "net/http"
9 |
10 | ginkgo "github.com/onsi/ginkgo/v2"
11 | gomega "github.com/onsi/gomega"
12 |
13 | "github.com/bankdata/styra-controller/pkg/styra"
14 | )
15 |
16 | var _ = ginkgo.Describe("DeletePolicy", func() {
17 |
18 | type test struct {
19 | policyName string
20 | responseCode int
21 | responseBody string
22 | expectedBody []byte
23 | expectStyraErr bool
24 | }
25 |
26 | ginkgo.DescribeTable("DeletePolicy", func(test test) {
27 | c := newTestClient(func(r *http.Request) *http.Response {
28 | bs, err := io.ReadAll(r.Body)
29 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
30 | gomega.Expect(bs).To(gomega.Equal([]byte("")))
31 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodDelete))
32 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/policies/" + test.policyName))
33 |
34 | return &http.Response{
35 | Header: make(http.Header),
36 | StatusCode: test.responseCode,
37 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)),
38 | }
39 | })
40 |
41 | res, err := c.DeletePolicy(context.Background(), test.policyName)
42 | if test.expectStyraErr {
43 | gomega.Expect(res).To(gomega.BeNil())
44 | target := &styra.HTTPError{}
45 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue())
46 | } else {
47 | gomega.Expect(err).ToNot(gomega.HaveOccurred())
48 | gomega.Expect(res.StatusCode).To(gomega.Equal(test.responseCode))
49 | gomega.Expect(res.Body).To(gomega.Equal(test.expectedBody))
50 | }
51 | },
52 |
53 | ginkgo.Entry("something", test{
54 | policyName: "policyname",
55 | responseCode: http.StatusOK,
56 | responseBody: `expected response from styra api`,
57 | expectedBody: []byte(`expected response from styra api`)},
58 | ),
59 |
60 | ginkgo.Entry("styra http error", test{
61 | policyName: "policyname",
62 | responseCode: http.StatusInternalServerError,
63 | expectStyraErr: true,
64 | }),
65 | )
66 | })
67 |
--------------------------------------------------------------------------------
/pkg/styra/secrets.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "io"
23 | "net/http"
24 |
25 | "github.com/pkg/errors"
26 | )
27 |
28 | const (
29 | endpointV1Secrets = "/v1/secrets"
30 | )
31 |
32 | // DeleteSecretResponse is the response type for calls to the
33 | // DELETE /v1/secrets/{secretId} endpoint in the Styra API.
34 | type DeleteSecretResponse struct {
35 | StatusCode int
36 | Body []byte
37 | }
38 |
39 | // CreateUpdateSecretResponse is the response type for calls to the
40 | // PUT /v1/secrets/{secretId} endpoint in the Styra API.
41 | type CreateUpdateSecretResponse struct {
42 | StatusCode int
43 | Body []byte
44 | }
45 |
46 | // CreateUpdateSecretsRequest is the response body for the
47 | // PUT /v1/secrets/{secretId} endpoint in the Styra API.
48 | type CreateUpdateSecretsRequest struct {
49 | Description string `json:"description"`
50 | Name string `json:"name"`
51 | Secret string `json:"secret"`
52 | }
53 |
54 | // CreateUpdateSecret calls the PUT /v1/secrets/{secretId} endpoint in the
55 | // Styra API.
56 | func (c *Client) CreateUpdateSecret(
57 | ctx context.Context,
58 | secretID string,
59 | createUpdateSecretsRequest *CreateUpdateSecretsRequest,
60 | ) (*CreateUpdateSecretResponse, error) {
61 | res, err := c.request(
62 | ctx,
63 | http.MethodPut,
64 | fmt.Sprintf("%s/%s", endpointV1Secrets, secretID),
65 | createUpdateSecretsRequest,
66 | nil,
67 | )
68 | if err != nil {
69 | return nil, err
70 | }
71 |
72 | body, err := io.ReadAll(res.Body)
73 | if err != nil {
74 | return nil, errors.Wrap(err, "could not read body")
75 | }
76 |
77 | if res.StatusCode != http.StatusOK {
78 | err := NewHTTPError(res.StatusCode, string(body))
79 | return nil, err
80 | }
81 |
82 | r := CreateUpdateSecretResponse{
83 | StatusCode: res.StatusCode,
84 | Body: body,
85 | }
86 |
87 | return &r, nil
88 | }
89 |
90 | // DeleteSecret calls the DELETE /v1/secrets/{secretId} endpoint in the
91 | // Styra API.
92 | func (c *Client) DeleteSecret(
93 | ctx context.Context,
94 | secretID string,
95 | ) (*DeleteSecretResponse, error) {
96 | res, err := c.request(
97 | ctx,
98 | http.MethodDelete,
99 | fmt.Sprintf("%s/%s", endpointV1Secrets, secretID),
100 | nil,
101 | nil,
102 | )
103 | if err != nil {
104 | return nil, err
105 | }
106 |
107 | body, err := io.ReadAll(res.Body)
108 | if err != nil {
109 | return nil, errors.Wrap(err, "could not read body")
110 | }
111 |
112 | if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNotFound {
113 | err := NewHTTPError(res.StatusCode, string(body))
114 | return nil, err
115 | }
116 |
117 | r := DeleteSecretResponse{
118 | StatusCode: res.StatusCode,
119 | Body: body,
120 | }
121 |
122 | return &r, nil
123 | }
124 |
--------------------------------------------------------------------------------
/pkg/styra/secrets_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra_test
18 |
19 | import (
20 | "bytes"
21 | "context"
22 | "encoding/json"
23 | "errors"
24 | "io"
25 | "net/http"
26 |
27 | ginkgo "github.com/onsi/ginkgo/v2"
28 | gomega "github.com/onsi/gomega"
29 |
30 | "github.com/bankdata/styra-controller/pkg/styra"
31 | )
32 |
33 | var _ = ginkgo.Describe("CreateUpdateSecret", func() {
34 | type test struct {
35 | secretID string
36 | responseCode int
37 | responseBody string
38 | createUpdateSecretsRequest *styra.CreateUpdateSecretsRequest
39 | expectStyraErr bool
40 | }
41 |
42 | ginkgo.DescribeTable("CreateUpdateSecret", func(test test) {
43 | c := newTestClient(func(r *http.Request) *http.Response {
44 | bs, err := io.ReadAll(r.Body)
45 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
46 | var b bytes.Buffer
47 | gomega.Expect(json.NewEncoder(&b).Encode(test.createUpdateSecretsRequest)).To(gomega.Succeed())
48 | gomega.Expect(bs).To(gomega.Equal((b.Bytes())))
49 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodPut))
50 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/secrets/" + test.secretID))
51 |
52 | return &http.Response{
53 | Header: make(http.Header),
54 | StatusCode: test.responseCode,
55 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)),
56 | }
57 | })
58 |
59 | res, err := c.CreateUpdateSecret(context.Background(), test.secretID, test.createUpdateSecretsRequest)
60 | if test.expectStyraErr {
61 | gomega.Expect(res).To(gomega.BeNil())
62 | target := &styra.HTTPError{}
63 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue())
64 | } else {
65 | gomega.Expect(err).ToNot(gomega.HaveOccurred())
66 | gomega.Expect(res.StatusCode).To(gomega.Equal(test.responseCode))
67 | }
68 |
69 | },
70 | ginkgo.Entry("something", test{
71 | secretID: "name",
72 | responseCode: http.StatusOK,
73 | responseBody: `{"test"}`,
74 | createUpdateSecretsRequest: &styra.CreateUpdateSecretsRequest{
75 | Description: "description",
76 | Name: "name",
77 | Secret: "secret",
78 | },
79 | }),
80 |
81 | ginkgo.Entry("styra http error", test{
82 | responseCode: http.StatusInternalServerError,
83 | expectStyraErr: true,
84 | }),
85 | )
86 | })
87 |
--------------------------------------------------------------------------------
/pkg/styra/styra.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2023 Bankdata.
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 styra holds a client and helpers for interacting with the Styra
18 | // APIs.
19 | package styra
20 |
--------------------------------------------------------------------------------
/pkg/styra/suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra_test
18 |
19 | import (
20 | "testing"
21 |
22 | ginkgo "github.com/onsi/ginkgo/v2"
23 | gomega "github.com/onsi/gomega"
24 | )
25 |
26 | func TestStyraClient(t *testing.T) {
27 | gomega.RegisterFailHandler(ginkgo.Fail)
28 | ginkgo.RunSpecs(t, "pkg/styra")
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/styra/users.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra
18 |
19 | import (
20 | "context"
21 | "encoding/json"
22 | "fmt"
23 | "io"
24 | "net/http"
25 |
26 | "github.com/pkg/errors"
27 | )
28 |
29 | const (
30 | endpointV1Users = "/v1/users"
31 | )
32 |
33 | // GetUserResponse is the response type for calls to the GET /v1/users/{userId} endpoint
34 | // in the Styra API.
35 | type GetUserResponse struct {
36 | StatusCode int
37 | Body []byte
38 | }
39 |
40 | // GetUsersResponse is the response type for calls to the GET /v1/users endpoint
41 | // in the Styra API.
42 | type GetUsersResponse struct {
43 | Users []User
44 | }
45 |
46 | // Struct to unmarshal the JSON response from the GET /v1/users endpoint
47 | type getUsersJSONResponse struct {
48 | Result []User `json:"result"`
49 | }
50 |
51 | // User is the struct for a user in the Styra API.
52 | type User struct {
53 | Enabled bool `json:"enabled"`
54 | ID string `json:"id"`
55 | }
56 |
57 | // GetUsers calls the GET /v1/users endpoint in the Styra API.
58 | func (c *Client) GetUsers(ctx context.Context) (*GetUsersResponse, bool, error) {
59 | const cacheKey = "allUsersResponse"
60 |
61 | // Check if the response is in the cache
62 | if cachedResponse, found := c.Cache.Get(cacheKey); found {
63 | return cachedResponse.(*GetUsersResponse), true, nil
64 | }
65 |
66 | res, err := c.GetUserEndpoint(ctx, endpointV1Users)
67 | if err != nil {
68 | return nil, false, err
69 | }
70 |
71 | var js getUsersJSONResponse
72 | if err := json.Unmarshal(res.Body, &js); err != nil {
73 | return nil, false, errors.Wrap(err, "could not unmarshal body: ")
74 | }
75 |
76 | r := GetUsersResponse{
77 | Users: js.Result,
78 | }
79 |
80 | // Cache the response
81 | c.Cache.Set(cacheKey, &r, 0)
82 |
83 | return &r, false, nil
84 | }
85 |
86 | // GetUser calls the GET /v1/users/{userId} endpoint in the Styra API.
87 | func (c *Client) GetUser(ctx context.Context, name string) (*GetUserResponse, error) {
88 | return c.GetUserEndpoint(ctx, fmt.Sprintf("%s/%s", endpointV1Users, name))
89 | }
90 |
91 | // GetUserEndpoint is a helper function to call the Styra API.
92 | func (c *Client) GetUserEndpoint(ctx context.Context, endpoint string) (*GetUserResponse, error) {
93 | res, err := c.request(ctx, http.MethodGet, endpoint, nil, nil)
94 | if err != nil {
95 | return nil, err
96 | }
97 |
98 | body, err := io.ReadAll(res.Body)
99 | if err != nil {
100 | return nil, errors.Wrap(err, "could not read body")
101 | }
102 |
103 | if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNotFound {
104 | err := NewHTTPError(res.StatusCode, string(body))
105 | return nil, err
106 | }
107 |
108 | r := GetUserResponse{
109 | StatusCode: res.StatusCode,
110 | Body: body,
111 | }
112 |
113 | return &r, nil
114 | }
115 |
--------------------------------------------------------------------------------
/pkg/styra/users_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra_test
18 |
19 | import (
20 | "bytes"
21 | "context"
22 | "errors"
23 | "io"
24 | "net/http"
25 | "time"
26 |
27 | ginkgo "github.com/onsi/ginkgo/v2"
28 | gomega "github.com/onsi/gomega"
29 | "github.com/patrickmn/go-cache"
30 |
31 | "github.com/bankdata/styra-controller/pkg/styra"
32 | )
33 |
34 | var _ = ginkgo.Describe("GetUser", func() {
35 |
36 | type test struct {
37 | name string
38 | responseCode int
39 | responseBody string
40 | expectStyraErr bool
41 | }
42 |
43 | ginkgo.DescribeTable("GetUser", func(test test) {
44 | c := newTestClient(func(r *http.Request) *http.Response {
45 | bs, err := io.ReadAll(r.Body)
46 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
47 | gomega.Expect(bs).To(gomega.Equal([]byte("")))
48 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodGet))
49 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/users/" + test.name))
50 |
51 | return &http.Response{
52 | Header: make(http.Header),
53 | StatusCode: test.responseCode,
54 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)),
55 | }
56 | })
57 |
58 | res, err := c.GetUser(context.Background(), test.name)
59 | if test.expectStyraErr {
60 | gomega.Expect(res).To(gomega.BeNil())
61 | target := &styra.HTTPError{}
62 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue())
63 | } else {
64 | gomega.Expect(err).ToNot(gomega.HaveOccurred())
65 | gomega.Expect(res.StatusCode).To(gomega.Equal(test.responseCode))
66 | }
67 | },
68 |
69 | ginkgo.Entry("something", test{
70 | name: "name",
71 | responseCode: http.StatusOK,
72 | responseBody: `{
73 | "request_id": "id",
74 | "result": {
75 | "enabled": false,
76 | "id": "name"
77 | }
78 | }`,
79 | }),
80 |
81 | ginkgo.Entry("styra http error", test{
82 | responseCode: http.StatusInternalServerError,
83 | expectStyraErr: true,
84 | }),
85 | )
86 | })
87 |
88 | var _ = ginkgo.Describe("GetUsers", func() {
89 | type test struct {
90 | responseCode int
91 | responseBody string
92 | expectStyraErr bool
93 | }
94 |
95 | ginkgo.DescribeTable("GetUsers",
96 | func(test test) {
97 | c := newTestClientWithCache(func(r *http.Request) *http.Response {
98 | bs, err := io.ReadAll(r.Body)
99 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
100 | gomega.Expect(bs).To(gomega.Equal([]byte("")))
101 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodGet))
102 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/users"))
103 |
104 | return &http.Response{
105 | Header: make(http.Header),
106 | StatusCode: test.responseCode,
107 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)),
108 | }
109 | }, cache.New(1*time.Hour, 10*time.Minute))
110 |
111 | // Call GetUsers
112 | res, _, err := c.GetUsers(context.Background())
113 | if test.expectStyraErr {
114 | gomega.Expect(res).To(gomega.BeNil())
115 | target := &styra.HTTPError{}
116 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue())
117 | } else {
118 | gomega.Expect(err).ToNot(gomega.HaveOccurred())
119 | gomega.Expect(res.Users).ToNot(gomega.BeNil())
120 | gomega.Expect(res.Users[0].ID).To(gomega.Equal("user1"))
121 | gomega.Expect(res.Users[0].Enabled).To(gomega.BeTrue())
122 | gomega.Expect(res.Users[1].ID).To(gomega.Equal("user2"))
123 | gomega.Expect(res.Users[1].Enabled).To(gomega.BeFalse())
124 | }
125 | },
126 |
127 | ginkgo.Entry("successful response", test{
128 | responseCode: http.StatusOK,
129 | responseBody: `{
130 | "result": [
131 | {"enabled": true, "id": "user1"},
132 | {"enabled": false, "id": "user2"}
133 | ]
134 | }`,
135 | }),
136 |
137 | ginkgo.Entry("styra http error", test{
138 | responseCode: http.StatusInternalServerError,
139 | expectStyraErr: true,
140 | }),
141 | )
142 | })
143 |
--------------------------------------------------------------------------------
/pkg/styra/workspace.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra
18 |
19 | import (
20 | "context"
21 | "io"
22 | "net/http"
23 |
24 | "github.com/pkg/errors"
25 | )
26 |
27 | const (
28 | endpointV1Workspace = "/v1/workspace"
29 | )
30 |
31 | // UpdateWorkspaceRequest is the request type for calls to the PUT /v1/workspace endpoint
32 | // in the Styra API.
33 | type UpdateWorkspaceRequest struct {
34 | DecisionsExporter *ExporterConfig `json:"decisions_exporter,omitempty"`
35 | ActivityExporter *ExporterConfig `json:"activity_exporter,omitempty"`
36 | }
37 |
38 | // UpdateWorkspaceResponse is the response type for calls to the PUT /v1/workspace endpoint
39 | // in the Styra API.
40 | type UpdateWorkspaceResponse struct {
41 | StatusCode int
42 | Body []byte
43 | }
44 |
45 | // ExporterConfig is the configuration for the decision and activity exporter in the Styra API.
46 | type ExporterConfig struct {
47 | Interval string `json:"interval,omitempty"`
48 | Kafka *KafkaConfig `json:"kafka,omitempty"`
49 | }
50 |
51 | // KafkaConfig is the configuration for the Kafka exporter in the Styra API.
52 | type KafkaConfig struct {
53 | Authentication string `json:"authentication"`
54 | Brokers []string `json:"brokers"`
55 | RequredAcks string `json:"required_acks"`
56 | Topic string `json:"topic"`
57 | TLS *KafkaTLS `json:"tls"`
58 | }
59 |
60 | // KafkaTLS is the TLS configuration for the Kafka exporter in the Styra API.
61 | type KafkaTLS struct {
62 | ClientCert string `json:"client_cert"`
63 | RootCA string `json:"rootca"`
64 | InsecureSkipVerify bool `json:"insecure_skip_verify"`
65 | }
66 |
67 | // UpdateWorkspace calls the PATCH /v1/workspace endpoint in the Styra API.
68 | func (c *Client) UpdateWorkspace(
69 | ctx context.Context,
70 | request *UpdateWorkspaceRequest,
71 | ) (*UpdateWorkspaceResponse, error) {
72 | return c.UpdateWorkspaceRaw(ctx, request)
73 | }
74 |
75 | // UpdateWorkspaceRaw calls the PATCH /v1/workspace endpoint in the Styra API.
76 | func (c *Client) UpdateWorkspaceRaw(
77 | ctx context.Context,
78 | request interface{},
79 | ) (*UpdateWorkspaceResponse, error) {
80 | res, err := c.request(ctx, http.MethodPatch, endpointV1Workspace, request, nil)
81 | if err != nil {
82 | return nil, err
83 | }
84 |
85 | body, err := io.ReadAll(res.Body)
86 | if err != nil {
87 | return nil, errors.Wrap(err, "could not read body")
88 | }
89 |
90 | if res.StatusCode != http.StatusOK {
91 | err := NewHTTPError(res.StatusCode, string(body))
92 | return nil, err
93 | }
94 |
95 | r := UpdateWorkspaceResponse{
96 | StatusCode: res.StatusCode,
97 | Body: body,
98 | }
99 |
100 | return &r, nil
101 | }
102 |
--------------------------------------------------------------------------------
/pkg/styra/workspace_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 styra_test
18 |
19 | import (
20 | "bytes"
21 | "context"
22 | "encoding/json"
23 | "errors"
24 | "io"
25 | "net/http"
26 |
27 | ginkgo "github.com/onsi/ginkgo/v2"
28 | gomega "github.com/onsi/gomega"
29 |
30 | "github.com/bankdata/styra-controller/pkg/styra"
31 | )
32 |
33 | var _ = ginkgo.Describe("UpdateWorkspace", func() {
34 |
35 | type test struct {
36 | request *styra.UpdateWorkspaceRequest
37 | responseCode int
38 | responseBody string
39 | expectStyraErr bool
40 | }
41 |
42 | ginkgo.DescribeTable("UpdateWorkspace", func(test test) {
43 | c := newTestClient(func(r *http.Request) *http.Response {
44 | bs, err := io.ReadAll(r.Body)
45 | gomega.Expect(err).NotTo(gomega.HaveOccurred())
46 |
47 | var b bytes.Buffer
48 | gomega.Expect(json.NewEncoder(&b).Encode(test.request)).To(gomega.Succeed())
49 | gomega.Expect(bs).To(gomega.Equal(b.Bytes()))
50 |
51 | gomega.Expect(r.Method).To(gomega.Equal(http.MethodPatch))
52 | gomega.Expect(r.URL.String()).To(gomega.Equal("http://test.com/v1/workspace"))
53 |
54 | return &http.Response{
55 | Header: make(http.Header),
56 | StatusCode: test.responseCode,
57 | Body: io.NopCloser(bytes.NewBufferString(test.responseBody)),
58 | }
59 | })
60 |
61 | res, err := c.UpdateWorkspace(context.Background(), test.request)
62 | if test.expectStyraErr {
63 | gomega.Expect(res).To(gomega.BeNil())
64 | target := &styra.HTTPError{}
65 | gomega.Expect(errors.As(err, &target)).To(gomega.BeTrue())
66 | } else {
67 | gomega.Expect(err).ToNot(gomega.HaveOccurred())
68 | gomega.Expect(res.StatusCode).To(gomega.Equal(test.responseCode))
69 | }
70 | },
71 |
72 | ginkgo.Entry("update workspace DecisionsExporter", test{
73 | request: &styra.UpdateWorkspaceRequest{
74 | DecisionsExporter: &styra.ExporterConfig{
75 | Interval: "1m",
76 | Kafka: &styra.KafkaConfig{
77 | Authentication: "auth",
78 | Brokers: []string{"broker"},
79 | RequredAcks: "acks",
80 | Topic: "topic",
81 | TLS: &styra.KafkaTLS{
82 | ClientCert: "clientcert",
83 | RootCA: "rootca",
84 | },
85 | },
86 | },
87 | },
88 | responseCode: http.StatusOK,
89 | responseBody: `{
90 | "request_id": "id"
91 | }`,
92 | }),
93 |
94 | ginkgo.Entry("update workspace ActivityExporter", test{
95 | request: &styra.UpdateWorkspaceRequest{
96 | ActivityExporter: &styra.ExporterConfig{
97 | Interval: "1m",
98 | Kafka: &styra.KafkaConfig{
99 | Authentication: "auth",
100 | Brokers: []string{"broker"},
101 | RequredAcks: "acks",
102 | Topic: "topic",
103 | TLS: &styra.KafkaTLS{
104 | ClientCert: "clientcert",
105 | RootCA: "rootca",
106 | InsecureSkipVerify: true,
107 | },
108 | },
109 | },
110 | },
111 | responseCode: http.StatusOK,
112 | responseBody: `{
113 | "request_id": "id"
114 | }`,
115 | }),
116 | ginkgo.Entry("styra http error", test{
117 | responseCode: http.StatusInternalServerError,
118 | expectStyraErr: true,
119 | }),
120 | )
121 | })
122 |
--------------------------------------------------------------------------------
/scripts/gen-api-docs/README.md:
--------------------------------------------------------------------------------
1 | `gen-crd-api-reference-docs` is a binary that can be used to generate documentation for CRDs. Repository is https://github.com/ahmetb/gen-crd-api-reference-docs.
2 |
3 | Download the binary to `./bin` by running `make gen-crd-api-reference-docs`.
4 |
5 | There exists `make` targets for generating all documentation and a `make` target for each API.
6 |
--------------------------------------------------------------------------------
/scripts/gen-api-docs/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "hideMemberFields": [
3 | "TypeMeta"
4 | ],
5 | "hideTypePatterns": [
6 | "ParseError$",
7 | "List$"
8 | ],
9 | "externalPackages": [
10 | {
11 | "typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$",
12 | "docsURLTemplate": "https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration"
13 | },
14 | {
15 | "typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/",
16 | "docsURLTemplate": "https://v1-20.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#{{lower .TypeIdentifier}}-{{arrIndex .PackageSegments -1}}-{{arrIndex .PackageSegments -2}}"
17 | }
18 | ],
19 | "markdownDisabled": false
20 | }
21 |
--------------------------------------------------------------------------------
/scripts/gen-api-docs/gen-api-docs.sh:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
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 | #!/usr/bin/env bash
16 |
17 | gen-styra-v1alpha1 () {
18 | echo "generating docs for api/styra/v1alpha1"
19 | ./bin/gen-crd-api-reference-docs \
20 | -config "scripts/gen-api-docs/config.json" \
21 | -api-dir "github.com/bankdata/styra-controller/api/styra/v1alpha1" \
22 | -template-dir "internal/template" \
23 | -out-file ./docs/apis/styra/v1alpha1.md
24 | }
25 |
26 |
27 | gen-styra-v1beta1 () {
28 | echo "generating docs for api/styra/v1beta1"
29 | ./bin/gen-crd-api-reference-docs \
30 | -config "scripts/gen-api-docs/config.json" \
31 | -api-dir "github.com/bankdata/styra-controller/api/styra/v1beta1" \
32 | -template-dir "internal/template" \
33 | -out-file ./docs/apis/styra/v1beta1.md
34 | }
35 |
36 | case $1 in
37 | styra-v1alpha1)
38 | gen-styra-v1alpha1
39 | ;;
40 | styra-v1beta1)
41 | gen-styra-v1beta1
42 | ;;
43 | all)
44 | gen-styra-v1alpha1
45 | gen-styra-v1beta1
46 | ;;
47 | *)
48 | echo "Usage: gen-api-docs.sh styra-v1alpha1|styra-v1beta1|all"
49 | ;;
50 | esac
51 |
--------------------------------------------------------------------------------
/tools.go:
--------------------------------------------------------------------------------
1 | //go:build tools
2 | // +build tools
3 |
4 | /*
5 | Copyright (C) 2023 Bankdata (bankdata@bankdata.dk)
6 |
7 | Licensed under the Apache License, Version 2.0 (the "License");
8 | you may not use this file except in compliance with the License.
9 | You may obtain a copy of the License at
10 |
11 | http://www.apache.org/licenses/LICENSE-2.0
12 |
13 | Unless required by applicable law or agreed to in writing, software
14 | distributed under the License is distributed on an "AS IS" BASIS,
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | See the License for the specific language governing permissions and
17 | limitations under the License.
18 | */
19 |
20 | package main
21 |
22 | import (
23 | _ "github.com/ahmetb/gen-crd-api-reference-docs"
24 | _ "github.com/golangci/golangci-lint/cmd/golangci-lint"
25 | _ "github.com/goreleaser/goreleaser"
26 | _ "github.com/onsi/ginkgo/v2/ginkgo"
27 | _ "github.com/vektra/mockery/v2"
28 | _ "sigs.k8s.io/controller-runtime/tools/setup-envtest"
29 | _ "sigs.k8s.io/controller-tools/cmd/controller-gen"
30 | _ "sigs.k8s.io/kind"
31 | _ "sigs.k8s.io/kustomize/kustomize/v5"
32 | )
33 |
--------------------------------------------------------------------------------