mirror of
https://github.com/cert-manager/webhook-example.git
synced 2025-07-02 23:05:48 +02:00
make conformance test pass
This commit is contained in:
parent
1798a826d4
commit
5639782790
10 changed files with 301 additions and 62 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -13,5 +13,7 @@
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
_out
|
||||||
|
|
||||||
# Ignore the built binary
|
# Ignore the built binary
|
||||||
cert-manager-webhook-example
|
cert-manager-webhook-sotoon
|
||||||
|
|
1
Makefile
1
Makefile
|
@ -6,6 +6,7 @@ OUT := $(shell pwd)/_out
|
||||||
$(shell mkdir -p "$(OUT)")
|
$(shell mkdir -p "$(OUT)")
|
||||||
|
|
||||||
verify:
|
verify:
|
||||||
|
sh ./scripts/fetch-test-binaries.sh
|
||||||
go test -v .
|
go test -v .
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
|
129
README.md
129
README.md
|
@ -1,40 +1,106 @@
|
||||||
# ACME webhook example
|
# SOTOON Webhook for Cert Manager
|
||||||
|
|
||||||
The ACME issuer type supports an optional 'webhook' solver, which can be used
|
This is a webhook solver for [Sotoon Cloud](https://sotoon.ir).
|
||||||
to implement custom DNS01 challenge solving logic.
|
|
||||||
|
|
||||||
This is useful if you need to use cert-manager with a DNS provider that is not
|
## Prerequisites
|
||||||
officially supported in cert-manager core.
|
|
||||||
|
|
||||||
## Why not in core?
|
* [cert-manager](https://github.com/jetstack/cert-manager) version 0.11.0 or higher (*tested with 0.12.0*):
|
||||||
|
- [Installing on Kubernetes](https://cert-manager.io/docs/installation/kubernetes/#installing-with-helm)
|
||||||
|
|
||||||
As the project & adoption has grown, there has been an influx of DNS provider
|
## Installation
|
||||||
pull requests to our core codebase. As this number has grown, the test matrix
|
|
||||||
has become un-maintainable and so, it's not possible for us to certify that
|
|
||||||
providers work to a sufficient level.
|
|
||||||
|
|
||||||
By creating this 'interface' between cert-manager and DNS providers, we allow
|
Choose a unique group name to identify your company or organization (for example `acme.mycompany.example`).
|
||||||
users to quickly iterate and test out new integrations, and then packaging
|
|
||||||
those up themselves as 'extensions' to cert-manager.
|
|
||||||
|
|
||||||
We can also then provide a standardised 'testing framework', or set of
|
```bash
|
||||||
conformance tests, which allow us to validate the a DNS provider works as
|
helm install ./deploy/cert-manager-webhook-sotoon \
|
||||||
expected.
|
--set groupName='<YOUR_UNIQUE_GROUP_NAME>'
|
||||||
|
```
|
||||||
|
|
||||||
## Creating your own webhook
|
If you customized the installation of cert-manager, you may need to also set the `certManager.namespace` and `certManager.serviceAccountName` values.
|
||||||
|
|
||||||
Webhook's themselves are deployed as Kubernetes API services, in order to allow
|
## Issuer
|
||||||
administrators to restrict access to webhooks with Kubernetes RBAC.
|
|
||||||
|
|
||||||
This is important, as otherwise it'd be possible for anyone with access to your
|
1. [Get your API token from Sotoon Panel](https://ocean.sotoon.ir/bepa/profile). The user whose api token is used must have `dns-editor` role:
|
||||||
webhook to complete ACME challenge validations and obtain certificates.
|
|
||||||
|
|
||||||
To make the set up of these webhook's easier, we provide a template repository
|
2. Create a secret to store your api token secret:
|
||||||
that can be used to get started quickly.
|
|
||||||
|
|
||||||
### Creating your own repository
|
```bash
|
||||||
|
kubectl create secret generic sotoon-credentials \
|
||||||
|
--from-literal=apiToken='<SOTOON_API_TOKEN>'
|
||||||
|
```
|
||||||
|
|
||||||
### Running the test suite
|
3. Grant permission to get the secret to the `cert-manager-webhook-sotoon` service account:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: cert-manager-webhook-sotoon:secret-reader
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
resourceNames: ["sotoon-credentials"]
|
||||||
|
verbs: ["get", "watch"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: cert-manager-webhook-sotoon:secret-reader
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: cert-manager-webhook-sotoon:secret-reader
|
||||||
|
subjects:
|
||||||
|
- apiGroup: ""
|
||||||
|
kind: ServiceAccount
|
||||||
|
name: cert-manager-webhook-sotoon
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Create a certificate issuer:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: cert-manager.io/v1alpha2
|
||||||
|
kind: Issuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
server: https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
email: '<YOUR_EMAIL_ADDRESS>'
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: letsencrypt-account-key
|
||||||
|
solvers:
|
||||||
|
- dns01:
|
||||||
|
webhook:
|
||||||
|
groupName: '<YOUR_UNIQUE_GROUP_NAME>'
|
||||||
|
solverName: sotoon
|
||||||
|
config:
|
||||||
|
endpoint: https://api.sotoon.ir
|
||||||
|
namespace: <SOTOON_NAMESPACE_OF_YOURS>
|
||||||
|
apiTokenSecretRef:
|
||||||
|
name: sotoon-credentials
|
||||||
|
key: apiToken
|
||||||
|
```
|
||||||
|
|
||||||
|
## Certificate
|
||||||
|
|
||||||
|
Issue a certificate:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: cert-manager.io/v1alpha2
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: example-com
|
||||||
|
spec:
|
||||||
|
dnsNames:
|
||||||
|
- example.com
|
||||||
|
- *.example.com
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt
|
||||||
|
secretName: example-com-tls
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
All DNS providers **must** run the DNS01 provider conformance testing suite,
|
All DNS providers **must** run the DNS01 provider conformance testing suite,
|
||||||
else they will have undetermined behaviour when used with cert-manager.
|
else they will have undetermined behaviour when used with cert-manager.
|
||||||
|
@ -44,11 +110,16 @@ DNS01 webhook.**
|
||||||
|
|
||||||
An example Go test file has been provided in [main_test.go]().
|
An example Go test file has been provided in [main_test.go]().
|
||||||
|
|
||||||
You can run the test suite with:
|
Before you can run the test suite, you need to download the test binaries:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ TEST_ZONE_NAME=example.com go test .
|
./scripts/fetch-test-binaries.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
The example file has a number of areas you must fill in and replace with your
|
Then duplicate the `.sample` files in `testdata/sotoon/` and update the configuration with the appropriate SOTOON credentials.
|
||||||
own options in order for tests to pass.
|
|
||||||
|
Now you can run the test suite with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TEST_ZONE_NAME=example.com. go test -v .
|
||||||
|
```
|
||||||
|
|
186
main.go
186
main.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
@ -26,12 +27,16 @@ var GroupName = os.Getenv("GROUP_NAME")
|
||||||
|
|
||||||
var validate *validator.Validate
|
var validate *validator.Validate
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
validate = validator.New()
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if GroupName == "" {
|
if GroupName == "" {
|
||||||
panic("GROUP_NAME must be specified")
|
panic("GROUP_NAME must be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
validate = validator.New()
|
fmt.Println("salaaaam")
|
||||||
|
|
||||||
// This will register our custom DNS provider with the webhook serving
|
// This will register our custom DNS provider with the webhook serving
|
||||||
// library, making it available as an API under the provided GroupName.
|
// library, making it available as an API under the provided GroupName.
|
||||||
|
@ -77,7 +82,6 @@ type sotoonDNSProviderConfig struct {
|
||||||
// These fields will be set by users in the
|
// These fields will be set by users in the
|
||||||
// `issuer.spec.acme.dns01.providers.webhook.config` field.
|
// `issuer.spec.acme.dns01.providers.webhook.config` field.
|
||||||
|
|
||||||
Email string `json:"email" validate:"email"`
|
|
||||||
Endpoint string `json:"endpoint" validate:"url"`
|
Endpoint string `json:"endpoint" validate:"url"`
|
||||||
Namespace string `json:"namespace" validate:"hostname_rfc1123"`
|
Namespace string `json:"namespace" validate:"hostname_rfc1123"`
|
||||||
APITokenSecretRef corev1.SecretKeySelector `json:"apiTokenSecretRef"`
|
APITokenSecretRef corev1.SecretKeySelector `json:"apiTokenSecretRef"`
|
||||||
|
@ -104,12 +108,16 @@ func (c *sotoonDNSProviderSolver) secret(ref corev1.SecretKeySelector, namespace
|
||||||
return string(bytes), nil
|
return string(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *sotoonDNSProviderSolver) sotoonClient(apiEndpoint, apiToken string) (*rest.RESTClient, error) {
|
func (c *sotoonDNSProviderSolver) sotoonClient(ch *v1alpha1.ChallengeRequest, cfg *sotoonDNSProviderConfig) (*rest.RESTClient, error) {
|
||||||
|
apiToken, err := c.secret(cfg.APITokenSecretRef, ch.ResourceNamespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
v1beta1.AddToScheme(scheme.Scheme)
|
v1beta1.AddToScheme(scheme.Scheme)
|
||||||
|
|
||||||
restConfig := &rest.Config{}
|
restConfig := &rest.Config{}
|
||||||
restConfig.Host = apiEndpoint
|
restConfig.Host = cfg.Endpoint
|
||||||
restConfig.APIPath = "/apis"
|
restConfig.APIPath = "/apis"
|
||||||
restConfig.BearerToken = apiToken
|
restConfig.BearerToken = apiToken
|
||||||
restConfig.ContentConfig.GroupVersion = &v1beta1.GroupVersion
|
restConfig.ContentConfig.GroupVersion = &v1beta1.GroupVersion
|
||||||
|
@ -129,6 +137,97 @@ func (c *sotoonDNSProviderSolver) Name() string {
|
||||||
return "sotoon"
|
return "sotoon"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRelevantZones(sotoonClient *rest.RESTClient, namespace, origin string) (*v1beta1.DomainZoneList, error) {
|
||||||
|
dzl := &v1beta1.DomainZoneList{}
|
||||||
|
|
||||||
|
if err := sotoonClient.
|
||||||
|
Get().
|
||||||
|
Namespace(namespace).
|
||||||
|
Resource("domainzones").
|
||||||
|
VersionedParams(&metav1.ListOptions{LabelSelector: fmt.Sprintf("dns.ravh.ir/origin=%s", origin)}, scheme.ParameterCodec).
|
||||||
|
Do(context.TODO()).
|
||||||
|
Into(dzl); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dzl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTXTRecord(sotoonClient *rest.RESTClient, zone *v1beta1.DomainZone, subdomain, target string) error {
|
||||||
|
if zone.Status.Status == "OK" {
|
||||||
|
if zone.Spec.Records == nil {
|
||||||
|
zone.Spec.Records = make(v1beta1.RecordsMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
records := zone.Spec.Records[subdomain]
|
||||||
|
if records == nil {
|
||||||
|
records = v1beta1.RecordList{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range records {
|
||||||
|
if r.TXT == target {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
records = append(records, v1beta1.Record{
|
||||||
|
SpecifiedType: "TXT",
|
||||||
|
TXT: target,
|
||||||
|
TTL: 30,
|
||||||
|
})
|
||||||
|
|
||||||
|
zone.Spec.Records[subdomain] = records
|
||||||
|
|
||||||
|
zoneData, err := json.Marshal(zone)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sotoonClient.Put().Name(zone.Name).Namespace(zone.Namespace).Resource("domainzones").Body(zoneData).Do(context.TODO()).Into(zone); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTXTRecord(sotoonClient *rest.RESTClient, zone *v1beta1.DomainZone, subdomain, target string) error {
|
||||||
|
if zone.Status.Status == "OK" {
|
||||||
|
if zone.Spec.Records == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
records := zone.Spec.Records[subdomain]
|
||||||
|
if records == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var newRecords v1beta1.RecordList
|
||||||
|
for _, r := range records {
|
||||||
|
if r.TXT != target {
|
||||||
|
newRecords = append(newRecords, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newRecords) == len(records) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
zone.Spec.Records[subdomain] = newRecords
|
||||||
|
|
||||||
|
zoneData, err := json.Marshal(zone)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sotoonClient.Put().Name(zone.Name).Namespace(zone.Namespace).Resource("domainzones").Body(zoneData).Do(context.TODO()).Into(zone); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Present is responsible for actually presenting the DNS record with the
|
// Present is responsible for actually presenting the DNS record with the
|
||||||
// DNS provider.
|
// DNS provider.
|
||||||
// This method should tolerate being called multiple times with the same value.
|
// This method should tolerate being called multiple times with the same value.
|
||||||
|
@ -140,37 +239,26 @@ func (c *sotoonDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cfg.validate(); err != nil {
|
sotoonClient, err := c.sotoonClient(ch, cfg)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
apiToken, err := c.secret(cfg.APITokenSecretRef, ch.ResourceNamespace)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
scl, err := c.sotoonClient(cfg.Endpoint, apiToken)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
origin := util.UnFqdn(ch.ResolvedZone)
|
origin := util.UnFqdn(ch.ResolvedZone)
|
||||||
|
zones, err := getRelevantZones(sotoonClient, cfg.Namespace, origin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
dzs := v1beta1.DomainZoneList{}
|
subdomain := getSubDomain(origin, ch.ResolvedFQDN)
|
||||||
scl.
|
target := ch.Key
|
||||||
Get().
|
|
||||||
Namespace(cfg.Namespace).
|
|
||||||
Resource("domainzones").
|
|
||||||
VersionedParams(&metav1.ListOptions{LabelSelector: fmt.Sprintf("dns.ravh.ir/origin=%s", origin)}, scheme.ParameterCodec).
|
|
||||||
Do(context.TODO()).
|
|
||||||
Into(&dzs)
|
|
||||||
|
|
||||||
fmt.Println(dzs)
|
for _, zone := range zones.Items {
|
||||||
|
if err := addTXTRecord(sotoonClient, &zone, subdomain, target); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: do something more useful with the decoded configuration
|
|
||||||
fmt.Printf("Decoded configuration %v", cfg)
|
|
||||||
|
|
||||||
// TODO: add code that sets a record in the DNS provider's console
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +269,31 @@ func (c *sotoonDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error {
|
||||||
// This is in order to facilitate multiple DNS validations for the same domain
|
// This is in order to facilitate multiple DNS validations for the same domain
|
||||||
// concurrently.
|
// concurrently.
|
||||||
func (c *sotoonDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error {
|
func (c *sotoonDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error {
|
||||||
|
cfg, err := loadConfig(ch.Config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sotoonClient, err := c.sotoonClient(ch, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
origin := util.UnFqdn(ch.ResolvedZone)
|
||||||
|
zones, err := getRelevantZones(sotoonClient, cfg.Namespace, origin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
subdomain := getSubDomain(origin, ch.ResolvedFQDN)
|
||||||
|
target := ch.Key
|
||||||
|
|
||||||
|
for _, zone := range zones.Items {
|
||||||
|
if err := removeTXTRecord(sotoonClient, &zone, subdomain, target); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: add code that deletes a record from the DNS provider's console
|
// TODO: add code that deletes a record from the DNS provider's console
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -207,16 +320,29 @@ func (c *sotoonDNSProviderSolver) Initialize(kubeClientConfig *rest.Config, stop
|
||||||
|
|
||||||
// loadConfig is a small helper function that decodes JSON configuration into
|
// loadConfig is a small helper function that decodes JSON configuration into
|
||||||
// the typed config struct.
|
// the typed config struct.
|
||||||
func loadConfig(cfgJSON *extapi.JSON) (sotoonDNSProviderConfig, error) {
|
func loadConfig(cfgJSON *extapi.JSON) (*sotoonDNSProviderConfig, error) {
|
||||||
cfg := sotoonDNSProviderConfig{}
|
cfg := &sotoonDNSProviderConfig{}
|
||||||
// handle the 'base case' where no configuration has been provided
|
// handle the 'base case' where no configuration has been provided
|
||||||
if cfgJSON == nil {
|
if cfgJSON == nil {
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(cfgJSON.Raw, &cfg); err != nil {
|
if err := json.Unmarshal(cfgJSON.Raw, cfg); err != nil {
|
||||||
return cfg, fmt.Errorf("error decoding solver config: %v", err)
|
return cfg, fmt.Errorf("error decoding solver config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := cfg.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// utils
|
||||||
|
func getSubDomain(domain, fqdn string) string {
|
||||||
|
if idx := strings.Index(fqdn, "."+domain); idx != -1 {
|
||||||
|
return fqdn[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.UnFqdn(fqdn)
|
||||||
|
}
|
||||||
|
|
|
@ -17,9 +17,10 @@ func TestRunsSuite(t *testing.T) {
|
||||||
// ChallengeRequest passed as part of the test cases.
|
// ChallengeRequest passed as part of the test cases.
|
||||||
|
|
||||||
fixture := dns.NewFixture(&sotoonDNSProviderSolver{},
|
fixture := dns.NewFixture(&sotoonDNSProviderSolver{},
|
||||||
|
dns.SetBinariesPath("_out/kubebuilder/bin"),
|
||||||
dns.SetResolvedZone(zone),
|
dns.SetResolvedZone(zone),
|
||||||
dns.SetAllowAmbientCredentials(false),
|
dns.SetAllowAmbientCredentials(false),
|
||||||
dns.SetManifestPath("testdata/my-custom-solver"),
|
dns.SetManifestPath("testdata/sotoon"),
|
||||||
)
|
)
|
||||||
|
|
||||||
fixture.RunConformance(t)
|
fixture.RunConformance(t)
|
||||||
|
|
|
@ -1 +1,25 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
k8s_version=1.14.1
|
||||||
|
arch=amd64
|
||||||
|
|
||||||
|
if [[ "$OSTYPE" == "linux-gnu" ]]; then
|
||||||
|
os="linux"
|
||||||
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
os="darwin"
|
||||||
|
else
|
||||||
|
echo "OS '$OSTYPE' not supported." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
root=$(cd "`dirname $0`"/..; pwd)
|
||||||
|
output_dir="$root"/_out
|
||||||
|
archive_name="kubebuilder-tools-$k8s_version-$os-$arch.tar.gz"
|
||||||
|
archive_file="$output_dir/$archive_name"
|
||||||
|
archive_url="https://storage.googleapis.com/kubebuilder-tools/$archive_name"
|
||||||
|
|
||||||
|
mkdir -p "$output_dir"
|
||||||
|
curl -sL "$archive_url" -o "$archive_file"
|
||||||
|
tar -zxf "$archive_file" -C "$output_dir/"
|
1
testdata/my-custom-solver/config.json
vendored
1
testdata/my-custom-solver/config.json
vendored
|
@ -1 +0,0 @@
|
||||||
{}
|
|
8
testdata/sotoon/config.json
vendored
Normal file
8
testdata/sotoon/config.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"endpoint": "<sotoon-api-url>",
|
||||||
|
"namespace": "<sotoon-namespace-of-yours>",
|
||||||
|
"apiTokenSecretRef": {
|
||||||
|
"Name": "sotoon-credentials",
|
||||||
|
"Key": "apiToken"
|
||||||
|
}
|
||||||
|
}
|
7
testdata/sotoon/sotoon-credentials.yaml
vendored
Normal file
7
testdata/sotoon/sotoon-credentials.yaml
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: sotoon-credentials
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
apiToken: <sotoon-api-token>
|
Loading…
Reference in a new issue