mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-15 17:51:01 +00:00
Merge pull request #365 from KianTigger/oracle-provider
Oracle provider
This commit is contained in:
commit
9d3b05a2c7
25 changed files with 950 additions and 1 deletions
|
@ -20,6 +20,7 @@ Multiple people and organizations are joining efforts to create a single Externa
|
|||
- [Yandex Lockbox](https://external-secrets.io/provider-yandex-lockbox/)
|
||||
- [Gitlab Project Variables](https://external-secrets.io/provider-gitlab-project-variables/)
|
||||
- [Alibaba Cloud KMS](https://www.alibabacloud.com/product/kms) (Docs still missing, PRs welcomed!)
|
||||
- [Oracle Vault]( https://external-secrets.io/provider-oracle-vault)
|
||||
|
||||
## Stability and Support Level
|
||||
|
||||
|
@ -40,7 +41,9 @@ Multiple people and organizations are joining efforts to create a single Externa
|
|||
| [IBM SM](https://external-secrets.io/provider-ibm-secrets-manager/) | alpha | @knelasevero @sebagomez @ricardoptcosta |
|
||||
| [Yandex Lockbox](https://external-secrets.io/provider-yandex-lockbox/) | alpha | @AndreyZamyslov @knelasevero |
|
||||
| [Gitlab Project Variables](https://external-secrets.io/provider-gitlab-project-variables/) | alpha | @Jabray5 |
|
||||
| Alibaba Cloud KMS | alpha | @ElsaChelala |
|
||||
| Alibaba Cloud KMS | alpha | @ElsaChelala |
|
||||
| [Oracle Vault]( https://external-secrets.io/provider-oracle-vault) | alpha | @KianTigger |
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
|
|
46
apis/externalsecrets/v1alpha1/secretstore_oracle_types.go
Normal file
46
apis/externalsecrets/v1alpha1/secretstore_oracle_types.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Configures an store to sync secrets using a Oracle Vault
|
||||
// backend.
|
||||
type OracleProvider struct {
|
||||
// Auth configures how secret-manager authenticates with the Oracle Vault.
|
||||
Auth OracleAuth `json:"auth"`
|
||||
|
||||
// User is an access OCID specific to the account.
|
||||
User string `json:"user,omitempty"`
|
||||
|
||||
// projectID is an access token specific to the secret.
|
||||
Tenancy string `json:"tenancy,omitempty"`
|
||||
|
||||
// projectID is an access token specific to the secret.
|
||||
Region string `json:"region,omitempty"`
|
||||
}
|
||||
|
||||
type OracleAuth struct {
|
||||
// SecretRef to pass through sensitive information.
|
||||
SecretRef OracleSecretRef `json:"secretRef"`
|
||||
}
|
||||
|
||||
type OracleSecretRef struct {
|
||||
// The Access Token is used for authentication
|
||||
PrivateKey esmeta.SecretKeySelector `json:"privatekey,omitempty"`
|
||||
|
||||
// projectID is an access token specific to the secret.
|
||||
Fingerprint esmeta.SecretKeySelector `json:"fingerprint,omitempty"`
|
||||
}
|
|
@ -50,6 +50,10 @@ type SecretStoreProvider struct {
|
|||
// +optional
|
||||
GCPSM *GCPSMProvider `json:"gcpsm,omitempty"`
|
||||
|
||||
// Oracle configures this store to sync secrets using Oracle Vault provider
|
||||
// +optional
|
||||
Oracle *OracleProvider `json:"oracle,omitempty"`
|
||||
|
||||
// IBM configures this store to sync secrets using IBM Cloud provider
|
||||
// +optional
|
||||
IBM *IBMProvider `json:"ibm,omitempty"`
|
||||
|
|
|
@ -658,6 +658,55 @@ func (in *IBMProvider) DeepCopy() *IBMProvider {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OracleAuth) DeepCopyInto(out *OracleAuth) {
|
||||
*out = *in
|
||||
in.SecretRef.DeepCopyInto(&out.SecretRef)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleAuth.
|
||||
func (in *OracleAuth) DeepCopy() *OracleAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OracleAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OracleProvider) DeepCopyInto(out *OracleProvider) {
|
||||
*out = *in
|
||||
in.Auth.DeepCopyInto(&out.Auth)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleProvider.
|
||||
func (in *OracleProvider) DeepCopy() *OracleProvider {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OracleProvider)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OracleSecretRef) DeepCopyInto(out *OracleSecretRef) {
|
||||
*out = *in
|
||||
in.PrivateKey.DeepCopyInto(&out.PrivateKey)
|
||||
in.Fingerprint.DeepCopyInto(&out.Fingerprint)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleSecretRef.
|
||||
func (in *OracleSecretRef) DeepCopy() *OracleSecretRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OracleSecretRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecretStore) DeepCopyInto(out *SecretStore) {
|
||||
*out = *in
|
||||
|
@ -740,6 +789,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
|
|||
*out = new(GCPSMProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Oracle != nil {
|
||||
in, out := &in.Oracle, &out.Oracle
|
||||
*out = new(OracleProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.IBM != nil {
|
||||
in, out := &in.IBM, &out.IBM
|
||||
*out = new(IBMProvider)
|
||||
|
|
|
@ -403,6 +403,76 @@ spec:
|
|||
required:
|
||||
- auth
|
||||
type: object
|
||||
oracle:
|
||||
description: Oracle configures this store to sync secrets using
|
||||
Oracle Vault provider
|
||||
properties:
|
||||
auth:
|
||||
description: Auth configures how secret-manager authenticates
|
||||
with the Oracle Vault.
|
||||
properties:
|
||||
secretRef:
|
||||
description: SecretRef to pass through sensitive information.
|
||||
properties:
|
||||
fingerprint:
|
||||
description: projectID is an access token specific
|
||||
to the secret.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret
|
||||
resource's `data` field to be used. Some instances
|
||||
of this field may be defaulted, in others it
|
||||
may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped.
|
||||
cluster-scoped defaults to the namespace of
|
||||
the referent.
|
||||
type: string
|
||||
type: object
|
||||
privatekey:
|
||||
description: The Access Token is used for authentication
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret
|
||||
resource's `data` field to be used. Some instances
|
||||
of this field may be defaulted, in others it
|
||||
may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped.
|
||||
cluster-scoped defaults to the namespace of
|
||||
the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- secretRef
|
||||
type: object
|
||||
region:
|
||||
description: projectID is an access token specific to the
|
||||
secret.
|
||||
type: string
|
||||
tenancy:
|
||||
description: projectID is an access token specific to the
|
||||
secret.
|
||||
type: string
|
||||
user:
|
||||
description: User is an access OCID specific to the account.
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
type: object
|
||||
vault:
|
||||
description: Vault configures this store to sync secrets using
|
||||
Hashi provider
|
||||
|
|
|
@ -403,6 +403,76 @@ spec:
|
|||
required:
|
||||
- auth
|
||||
type: object
|
||||
oracle:
|
||||
description: Oracle configures this store to sync secrets using
|
||||
Oracle Vault provider
|
||||
properties:
|
||||
auth:
|
||||
description: Auth configures how secret-manager authenticates
|
||||
with the Oracle Vault.
|
||||
properties:
|
||||
secretRef:
|
||||
description: SecretRef to pass through sensitive information.
|
||||
properties:
|
||||
fingerprint:
|
||||
description: projectID is an access token specific
|
||||
to the secret.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret
|
||||
resource's `data` field to be used. Some instances
|
||||
of this field may be defaulted, in others it
|
||||
may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped.
|
||||
cluster-scoped defaults to the namespace of
|
||||
the referent.
|
||||
type: string
|
||||
type: object
|
||||
privatekey:
|
||||
description: The Access Token is used for authentication
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret
|
||||
resource's `data` field to be used. Some instances
|
||||
of this field may be defaulted, in others it
|
||||
may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped.
|
||||
cluster-scoped defaults to the namespace of
|
||||
the referent.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- secretRef
|
||||
type: object
|
||||
region:
|
||||
description: projectID is an access token specific to the
|
||||
secret.
|
||||
type: string
|
||||
tenancy:
|
||||
description: projectID is an access token specific to the
|
||||
secret.
|
||||
type: string
|
||||
user:
|
||||
description: User is an access OCID specific to the account.
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
type: object
|
||||
vault:
|
||||
description: Vault configures this store to sync secrets using
|
||||
Hashi provider
|
||||
|
|
BIN
docs/pictures/screenshot_API_key.png
Normal file
BIN
docs/pictures/screenshot_API_key.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 KiB |
BIN
docs/pictures/screenshot_fingerprint.png
Normal file
BIN
docs/pictures/screenshot_fingerprint.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
docs/pictures/screenshot_region.png
Normal file
BIN
docs/pictures/screenshot_region.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
docs/pictures/screenshot_tenancy_OCID.png
Normal file
BIN
docs/pictures/screenshot_tenancy_OCID.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
BIN
docs/pictures/screenshot_user_OCID.png
Normal file
BIN
docs/pictures/screenshot_user_OCID.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 89 KiB |
54
docs/provider-oracle-vault.md
Normal file
54
docs/provider-oracle-vault.md
Normal file
|
@ -0,0 +1,54 @@
|
|||
## Oracle Vault
|
||||
|
||||
External Secrets Operator integrates with [OCI API](https://github.com/oracle/oci-go-sdk) to sync secret on the Oracle Vault to secrets held on the Kubernetes cluster.
|
||||
|
||||
### Authentication
|
||||
|
||||
The API requires a userOCID, tenancyOCID, fingerprint, key file and a region. The fingerprint and key file should be supplied in the secret with the rest being provided in the secret store.
|
||||
|
||||
See url for what region you you are accessing.
|
||||
![userOCID-details](./pictures/screenshot_region.png)
|
||||
|
||||
Select tenancy in the top right to see your user OCID as shown below.
|
||||
![tenancyOCID-details](./pictures/tenancy.png)
|
||||
|
||||
Select your user in the top right to see your user OCID as shown below.
|
||||
![region-details](./pictures/screenshot_user_OCID.png)
|
||||
|
||||
|
||||
#### Service account key authentication
|
||||
|
||||
Create a secret containing your private key and fingerprint:
|
||||
|
||||
```yaml
|
||||
{% include 'oracle-credentials-secret.yaml' %}
|
||||
```
|
||||
|
||||
Your fingerprint will be attatched to your API key, once it has been generated. Found on the same page as the user OCID.
|
||||
![fingerprint-details](./pictures/screenshot_fingerprint.png)
|
||||
|
||||
Once you click "Add API Key" you will be shown the following, where you can download the RSA key in the necessary PEM format for API requests.
|
||||
This will automatically generate a fingerprint.
|
||||
![API-key-details](./pictures/screenshot_API_key.png)
|
||||
|
||||
### Update secret store
|
||||
Be sure the `oracle` provider is listed in the `Kind=SecretStore`
|
||||
|
||||
```yaml
|
||||
{% include 'oracle-secret-store.yaml' %}
|
||||
```
|
||||
|
||||
### Creating external secret
|
||||
|
||||
To create a kubernetes secret from the Oracle Cloud Interface secret a`Kind=ExternalSecret` is needed.
|
||||
|
||||
```yaml
|
||||
{% include 'oracle-external-secret.yaml' %}
|
||||
```
|
||||
|
||||
|
||||
### Getting the Kubernetes secret
|
||||
The operator will fetch the project variable and inject it as a `Kind=Secret`.
|
||||
```
|
||||
kubectl get secret oracle-secret-to-create -o jsonpath='{.data.dev-secret-test}' | base64 -d
|
||||
```
|
10
docs/snippets/oracle-credentials-secret.yaml
Normal file
10
docs/snippets/oracle-credentials-secret.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: oracle-secret
|
||||
labels:
|
||||
type: oracle
|
||||
type: Opaque
|
||||
stringData:
|
||||
privateKey:
|
||||
fingerprint:
|
16
docs/snippets/oracle-external-secret.yaml
Normal file
16
docs/snippets/oracle-external-secret.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
apiVersion: external-secrets.io/v1alpha1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
refreshInterval: 0.03m
|
||||
secretStoreRef:
|
||||
kind: SecretStore
|
||||
name: example # Must match SecretStore on the cluster
|
||||
target:
|
||||
name: secret-to-be-created # Name for the secret on the cluster
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey:
|
||||
remoteRef:
|
||||
key:
|
18
docs/snippets/oracle-secret-store.yaml
Normal file
18
docs/snippets/oracle-secret-store.yaml
Normal file
|
@ -0,0 +1,18 @@
|
|||
apiVersion: external-secrets.io/v1alpha1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
provider:
|
||||
oracle: #Needs to match value in secretstore_types.go
|
||||
user:
|
||||
tenancy:
|
||||
region:
|
||||
auth:
|
||||
secretRef:
|
||||
privatekey:
|
||||
name: oracle-secret
|
||||
key: privateKey #Needs to match stringData val in secret_oracle.yml
|
||||
fingerprint:
|
||||
name: oracle-secret
|
||||
key: fingerprint
|
|
@ -60,5 +60,10 @@ kubectl run --rm \
|
|||
--env="VAULT_URL=${VAULT_URL:-}" \
|
||||
--env="GITLAB_TOKEN=${GITLAB_TOKEN:-}" \
|
||||
--env="GITLAB_PROJECT_ID=${GITLAB_PROJECT_ID:-}" \
|
||||
--env="ORACLE_USER_OCID=${ORACLE_USER_OCID:-}" \
|
||||
--env="ORACLE_TENANCY_OCID=${ORACLE_TENANCY_OCID:-}" \
|
||||
--env="ORACLE_REGION=${ORACLE_REGION:-}" \
|
||||
--env="ORACLE_FINGERPRINT=${ORACLE_FINGERPRINT:-}" \
|
||||
--env="ORACLE_KEY=${ORACLE_KEY:-}" \
|
||||
--overrides='{ "apiVersion": "v1", "spec":{"serviceAccountName": "external-secrets-e2e"}}' \
|
||||
e2e --image=local/external-secrets-e2e:test
|
||||
|
|
47
e2e/suite/oracle/oracle.go
Normal file
47
e2e/suite/oracle/oracle.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
limitations under the License.
|
||||
*/
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
// nolint
|
||||
. "github.com/onsi/ginkgo"
|
||||
// nolint
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
|
||||
"github.com/external-secrets/external-secrets/e2e/framework"
|
||||
"github.com/external-secrets/external-secrets/e2e/suite/common"
|
||||
)
|
||||
|
||||
var _ = Describe("[oracle] ", func() {
|
||||
f := framework.New("eso-oracle")
|
||||
tenancy := os.Getenv("OCI_TENANCY_OCID")
|
||||
user := os.Getenv("OCI_USER_OCID")
|
||||
region := os.Getenv("OCI_REGION")
|
||||
fingerprint := os.Getenv("OCI_FINGERPRINT")
|
||||
privateKey := os.Getenv("OCI_PRIVATE_KEY")
|
||||
prov := newOracleProvider(f, tenancy, user, region, fingerprint, privateKey)
|
||||
|
||||
DescribeTable("sync secrets", framework.TableFunc(f, prov),
|
||||
Entry(common.SimpleDataSync(f)),
|
||||
Entry(common.NestedJSONWithGJSON(f)),
|
||||
Entry(common.JSONDataFromSync(f)),
|
||||
Entry(common.JSONDataWithProperty(f)),
|
||||
Entry(common.JSONDataWithTemplate(f)),
|
||||
Entry(common.DockerJSONConfig(f)),
|
||||
Entry(common.DataPropertyDockerconfigJSON(f)),
|
||||
Entry(common.SSHKeySync(f)),
|
||||
Entry(common.SSHKeySyncDataProperty(f)),
|
||||
)
|
||||
})
|
124
e2e/suite/oracle/provider.go
Normal file
124
e2e/suite/oracle/provider.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
limitations under the License.
|
||||
*/
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
// nolint
|
||||
. "github.com/onsi/ginkgo"
|
||||
|
||||
// nolint
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/oracle/oci-go-sdk/v45/common"
|
||||
vault "github.com/oracle/oci-go-sdk/v45/vault"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
|
||||
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
"github.com/external-secrets/external-secrets/e2e/framework"
|
||||
)
|
||||
|
||||
type oracleProvider struct {
|
||||
tenancy string
|
||||
user string
|
||||
region string
|
||||
fingerprint string
|
||||
privateKey string
|
||||
framework *framework.Framework
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
const (
|
||||
secretName = "secretName"
|
||||
)
|
||||
|
||||
func newOracleProvider(f *framework.Framework, tenancy, user, region, fingerprint, privateKey string) *oracleProvider {
|
||||
prov := &oracleProvider{
|
||||
tenancy: tenancy,
|
||||
user: user,
|
||||
region: region,
|
||||
fingerprint: fingerprint,
|
||||
privateKey: privateKey,
|
||||
framework: f,
|
||||
}
|
||||
BeforeEach(prov.BeforeEach)
|
||||
return prov
|
||||
}
|
||||
|
||||
func (p *oracleProvider) CreateSecret(key, val string) {
|
||||
configurationProvider := common.NewRawConfigurationProvider(p.tenancy, p.user, p.region, p.fingerprint, p.privateKey, nil)
|
||||
client, err := vault.NewVaultsClientWithConfigurationProvider(configurationProvider)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
vmssecretrequest := vault.CreateSecretRequest{}
|
||||
vmssecretrequest.SecretName = utilpointer.StringPtr(secretName)
|
||||
vmssecretrequest.SecretContent = vault.Base64SecretContentDetails{
|
||||
Name: utilpointer.StringPtr(key),
|
||||
Content: utilpointer.StringPtr(val),
|
||||
}
|
||||
_, err = client.CreateSecret(p.ctx, vmssecretrequest)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func (p *oracleProvider) DeleteSecret(key string) {
|
||||
configurationProvider := common.NewRawConfigurationProvider(p.tenancy, p.user, p.region, p.fingerprint, p.privateKey, nil)
|
||||
client, err := vault.NewVaultsClientWithConfigurationProvider(configurationProvider)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
vmssecretrequest := vault.ScheduleSecretDeletionRequest{}
|
||||
vmssecretrequest.SecretId = utilpointer.StringPtr(key)
|
||||
_, err = client.ScheduleSecretDeletion(p.ctx, vmssecretrequest)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func (p *oracleProvider) BeforeEach() {
|
||||
OracleCreds := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Namespace: p.framework.Namespace.Name,
|
||||
},
|
||||
StringData: map[string]string{
|
||||
secretName: "value",
|
||||
},
|
||||
}
|
||||
err := p.framework.CRClient.Create(context.Background(), OracleCreds)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
secretStore := &esv1alpha1.SecretStore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: p.framework.Namespace.Name,
|
||||
Namespace: p.framework.Namespace.Name,
|
||||
},
|
||||
Spec: esv1alpha1.SecretStoreSpec{
|
||||
Provider: &esv1alpha1.SecretStoreProvider{
|
||||
Oracle: &esv1alpha1.OracleProvider{
|
||||
Auth: esv1alpha1.OracleAuth{
|
||||
SecretRef: esv1alpha1.OracleSecretRef{
|
||||
Fingerprint: esmeta.SecretKeySelector{
|
||||
Name: "vms-secret",
|
||||
Key: "keyid",
|
||||
},
|
||||
PrivateKey: esmeta.SecretKeySelector{
|
||||
Name: "vms-secret",
|
||||
Key: "accesskey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err = p.framework.CRClient.Create(context.Background(), secretStore)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
1
go.mod
1
go.mod
|
@ -59,6 +59,7 @@ require (
|
|||
github.com/lestrrat-go/jwx v1.2.1
|
||||
github.com/onsi/ginkgo v1.16.4
|
||||
github.com/onsi/gomega v1.16.0
|
||||
github.com/oracle/oci-go-sdk/v45 v45.2.0
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
|
|
2
go.sum
2
go.sum
|
@ -545,6 +545,8 @@ github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDs
|
|||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
|
||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/oracle/oci-go-sdk/v45 v45.2.0 h1:vCPoQlE+DOrM2heJn66rvPU6fbsc/0Cxtzs2jnFut6U=
|
||||
github.com/oracle/oci-go-sdk/v45 v45.2.0/go.mod h1:ZM6LGiRO5TPQJxTlrXbcHMbClE775wnGD5U/EerCsRw=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
|
|
|
@ -50,6 +50,8 @@ nav:
|
|||
- Lockbox: provider-yandex-lockbox.md
|
||||
- Gitlab:
|
||||
- Gitlab Project Variables: provider-gitlab-project-variables.md
|
||||
- Oracle:
|
||||
- Oracle Vault: provider-oracle-vault.md
|
||||
- References:
|
||||
- API specification: spec.md
|
||||
- Contributing:
|
||||
|
|
36
pkg/provider/oracle/fake/fake.go
Normal file
36
pkg/provider/oracle/fake/fake.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package fake
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
vault "github.com/oracle/oci-go-sdk/v45/vault"
|
||||
)
|
||||
|
||||
type OracleMockClient struct {
|
||||
getSecret func(ctx context.Context, request vault.GetSecretRequest) (response vault.GetSecretResponse, err error)
|
||||
}
|
||||
|
||||
func (mc *OracleMockClient) GetSecret(ctx context.Context, request vault.GetSecretRequest) (response vault.GetSecretResponse, err error) {
|
||||
return mc.getSecret(ctx, request)
|
||||
}
|
||||
|
||||
func (mc *OracleMockClient) WithValue(input vault.GetSecretRequest, output vault.GetSecretResponse, err error) {
|
||||
if mc != nil {
|
||||
mc.getSecret = func(ctx context.Context, paramReq vault.GetSecretRequest) (vault.GetSecretResponse, error) {
|
||||
return output, err
|
||||
}
|
||||
}
|
||||
}
|
213
pkg/provider/oracle/oracle.go
Normal file
213
pkg/provider/oracle/oracle.go
Normal file
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/oracle/oci-go-sdk/v45/common"
|
||||
vault "github.com/oracle/oci-go-sdk/v45/vault"
|
||||
"github.com/tidwall/gjson"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
VaultEndpointEnv = "ORACLE_VAULT_ENDPOINT"
|
||||
STSEndpointEnv = "ORACLE_STS_ENDPOINT"
|
||||
SVMEndpointEnv = "ORACLE_SVM_ENDPOINT"
|
||||
|
||||
errOracleClient = "cannot setup new oracle client: %w"
|
||||
errORACLECredSecretName = "invalid oracle SecretStore resource: missing oracle APIKey"
|
||||
errUninitalizedOracleProvider = "provider oracle is not initialized"
|
||||
errInvalidClusterStoreMissingSKNamespace = "invalid ClusterStore, missing namespace"
|
||||
errFetchSAKSecret = "could not fetch SecretAccessKey secret: %w"
|
||||
errMissingPK = "missing PrivateKey"
|
||||
errMissingUser = "missing User ID"
|
||||
errMissingTenancy = "missing Tenancy ID"
|
||||
errMissingRegion = "missing Region"
|
||||
errMissingFingerprint = "missing Fingerprint"
|
||||
errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
|
||||
errMissingKey = "missing Key in secret: %s"
|
||||
errInvalidSecret = "invalid secret received. no secret string nor binary for key: %s"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
kube kclient.Client
|
||||
store *esv1alpha1.OracleProvider
|
||||
namespace string
|
||||
storeKind string
|
||||
tenancy string
|
||||
user string
|
||||
region string
|
||||
fingerprint string
|
||||
privateKey string
|
||||
}
|
||||
|
||||
type VaultManagementService struct {
|
||||
Client VMInterface
|
||||
}
|
||||
|
||||
type VMInterface interface {
|
||||
GetSecret(ctx context.Context, request vault.GetSecretRequest) (response vault.GetSecretResponse, err error)
|
||||
}
|
||||
|
||||
func (c *client) setAuth(ctx context.Context) error {
|
||||
credentialsSecret := &corev1.Secret{}
|
||||
credentialsSecretName := c.store.Auth.SecretRef.PrivateKey.Name
|
||||
if credentialsSecretName == "" {
|
||||
return fmt.Errorf(errORACLECredSecretName)
|
||||
}
|
||||
objectKey := types.NamespacedName{
|
||||
Name: credentialsSecretName,
|
||||
Namespace: c.namespace,
|
||||
}
|
||||
|
||||
// only ClusterStore is allowed to set namespace (and then it's required)
|
||||
if c.storeKind == esv1alpha1.ClusterSecretStoreKind {
|
||||
if c.store.Auth.SecretRef.PrivateKey.Namespace == nil {
|
||||
return fmt.Errorf(errInvalidClusterStoreMissingSKNamespace)
|
||||
}
|
||||
objectKey.Namespace = *c.store.Auth.SecretRef.PrivateKey.Namespace
|
||||
}
|
||||
|
||||
err := c.kube.Get(ctx, objectKey, credentialsSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errFetchSAKSecret, err)
|
||||
}
|
||||
|
||||
c.privateKey = string(credentialsSecret.Data[c.store.Auth.SecretRef.PrivateKey.Key])
|
||||
if c.privateKey == "" {
|
||||
return fmt.Errorf(errMissingPK)
|
||||
}
|
||||
|
||||
c.fingerprint = string(credentialsSecret.Data[c.store.Auth.SecretRef.Fingerprint.Key])
|
||||
if c.fingerprint == "" {
|
||||
return fmt.Errorf(errMissingFingerprint)
|
||||
}
|
||||
|
||||
c.user = c.store.User
|
||||
if c.user == "" {
|
||||
return fmt.Errorf(errMissingUser)
|
||||
}
|
||||
|
||||
c.tenancy = c.store.Tenancy
|
||||
if c.tenancy == "" {
|
||||
return fmt.Errorf(errMissingTenancy)
|
||||
}
|
||||
|
||||
c.region = c.store.Region
|
||||
if c.region == "" {
|
||||
return fmt.Errorf(errMissingRegion)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vms *VaultManagementService) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
if utils.IsNil(vms.Client) {
|
||||
return nil, fmt.Errorf(errUninitalizedOracleProvider)
|
||||
}
|
||||
vmsRequest := vault.GetSecretRequest{
|
||||
SecretId: &ref.Key,
|
||||
}
|
||||
secretOut, err := vms.Client.GetSecret(context.Background(), vmsRequest)
|
||||
if err != nil {
|
||||
return nil, util.SanitizeErr(err)
|
||||
}
|
||||
if ref.Property == "" {
|
||||
if *secretOut.SecretName != "" {
|
||||
return []byte(*secretOut.SecretName), nil
|
||||
}
|
||||
return nil, fmt.Errorf(errInvalidSecret, ref.Key)
|
||||
}
|
||||
var payload *string
|
||||
if secretOut.SecretName != nil {
|
||||
payload = secretOut.SecretName
|
||||
}
|
||||
|
||||
payloadval := *payload
|
||||
|
||||
val := gjson.Get(payloadval, ref.Property)
|
||||
if !val.Exists() {
|
||||
return nil, fmt.Errorf(errMissingKey, ref.Key)
|
||||
}
|
||||
|
||||
return []byte(val.String()), nil
|
||||
}
|
||||
|
||||
func (vms *VaultManagementService) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
data, err := vms.GetSecret(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kv := make(map[string]string)
|
||||
err = json.Unmarshal(data, &kv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
|
||||
}
|
||||
secretData := make(map[string][]byte)
|
||||
for k, v := range kv {
|
||||
secretData[k] = []byte(v)
|
||||
}
|
||||
return secretData, nil
|
||||
}
|
||||
|
||||
// NewClient constructs a new secrets client based on the provided store.
|
||||
func (vms *VaultManagementService) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
oracleSpec := storeSpec.Provider.Oracle
|
||||
|
||||
oracleStore := &client{
|
||||
kube: kube,
|
||||
store: oracleSpec,
|
||||
namespace: namespace,
|
||||
storeKind: store.GetObjectKind().GroupVersionKind().Kind,
|
||||
}
|
||||
if err := oracleStore.setAuth(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oracleTenancy := oracleStore.tenancy
|
||||
oracleUser := oracleStore.user
|
||||
oracleRegion := oracleStore.region
|
||||
oracleFingerprint := oracleStore.fingerprint
|
||||
oraclePrivateKey := oracleStore.privateKey
|
||||
|
||||
configurationProvider := common.NewRawConfigurationProvider(oracleTenancy, oracleUser, oracleRegion, oracleFingerprint, oraclePrivateKey, nil)
|
||||
|
||||
vaultManagementService, err := vault.NewVaultsClientWithConfigurationProvider(configurationProvider)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errOracleClient, err)
|
||||
}
|
||||
vms.Client = vaultManagementService
|
||||
return vms, nil
|
||||
}
|
||||
|
||||
func (vms *VaultManagementService) Close(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&VaultManagementService{}, &esv1alpha1.SecretStoreProvider{
|
||||
Oracle: &esv1alpha1.OracleProvider{},
|
||||
})
|
||||
}
|
173
pkg/provider/oracle/oracle_test.go
Normal file
173
pkg/provider/oracle/oracle_test.go
Normal file
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
vault "github.com/oracle/oci-go-sdk/v45/vault"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
|
||||
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||
fakeoracle "github.com/external-secrets/external-secrets/pkg/provider/oracle/fake"
|
||||
)
|
||||
|
||||
type vaultTestCase struct {
|
||||
mockClient *fakeoracle.OracleMockClient
|
||||
apiInput *vault.GetSecretRequest
|
||||
apiOutput *vault.GetSecretResponse
|
||||
ref *esv1alpha1.ExternalSecretDataRemoteRef
|
||||
apiErr error
|
||||
expectError string
|
||||
expectedSecret string
|
||||
// for testing secretmap
|
||||
expectedData map[string][]byte
|
||||
}
|
||||
|
||||
func makeValidVaultTestCase() *vaultTestCase {
|
||||
smtc := vaultTestCase{
|
||||
mockClient: &fakeoracle.OracleMockClient{},
|
||||
apiInput: makeValidAPIInput(),
|
||||
ref: makeValidRef(),
|
||||
apiOutput: makeValidAPIOutput(),
|
||||
apiErr: nil,
|
||||
expectError: "",
|
||||
expectedSecret: "",
|
||||
expectedData: map[string][]byte{},
|
||||
}
|
||||
smtc.mockClient.WithValue(*smtc.apiInput, *smtc.apiOutput, smtc.apiErr)
|
||||
return &smtc
|
||||
}
|
||||
|
||||
func makeValidRef() *esv1alpha1.ExternalSecretDataRemoteRef {
|
||||
return &esv1alpha1.ExternalSecretDataRemoteRef{
|
||||
Key: "test-secret",
|
||||
Version: "default",
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidAPIInput() *vault.GetSecretRequest {
|
||||
return &vault.GetSecretRequest{
|
||||
SecretId: utilpointer.StringPtr("test-secret"),
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidAPIOutput() *vault.GetSecretResponse {
|
||||
return &vault.GetSecretResponse{
|
||||
Etag: utilpointer.StringPtr("test-name"),
|
||||
Secret: vault.Secret{},
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidVaultTestCaseCustom(tweaks ...func(smtc *vaultTestCase)) *vaultTestCase {
|
||||
smtc := makeValidVaultTestCase()
|
||||
for _, fn := range tweaks {
|
||||
fn(smtc)
|
||||
}
|
||||
smtc.mockClient.WithValue(*smtc.apiInput, *smtc.apiOutput, smtc.apiErr)
|
||||
return smtc
|
||||
}
|
||||
|
||||
// This case can be shared by both GetSecret and GetSecretMap tests.
|
||||
// bad case: set apiErr.
|
||||
var setAPIErr = func(smtc *vaultTestCase) {
|
||||
smtc.apiErr = fmt.Errorf("oh no")
|
||||
smtc.expectError = "oh no"
|
||||
}
|
||||
|
||||
var setNilMockClient = func(smtc *vaultTestCase) {
|
||||
smtc.mockClient = nil
|
||||
smtc.expectError = errUninitalizedOracleProvider
|
||||
}
|
||||
|
||||
func TestOracleVaultGetSecret(t *testing.T) {
|
||||
secretValue := "changedvalue"
|
||||
// good case: default version is set
|
||||
// key is passed in, output is sent back
|
||||
setSecretString := func(smtc *vaultTestCase) {
|
||||
smtc.apiOutput = &vault.GetSecretResponse{
|
||||
Etag: utilpointer.StringPtr("test-name"),
|
||||
Secret: vault.Secret{
|
||||
CompartmentId: utilpointer.StringPtr("test-compartment-id"),
|
||||
Id: utilpointer.StringPtr("test-id"),
|
||||
SecretName: utilpointer.StringPtr("changedvalue"),
|
||||
},
|
||||
}
|
||||
smtc.expectedSecret = secretValue
|
||||
}
|
||||
|
||||
successCases := []*vaultTestCase{
|
||||
makeValidVaultTestCaseCustom(setAPIErr),
|
||||
makeValidVaultTestCaseCustom(setNilMockClient),
|
||||
makeValidVaultTestCaseCustom(setSecretString),
|
||||
}
|
||||
|
||||
sm := VaultManagementService{}
|
||||
for k, v := range successCases {
|
||||
sm.Client = v.mockClient
|
||||
fmt.Println(*v.ref)
|
||||
out, err := sm.GetSecret(context.Background(), *v.ref)
|
||||
if !ErrorContains(err, v.expectError) {
|
||||
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
|
||||
}
|
||||
if string(out) != v.expectedSecret {
|
||||
t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSecretMap(t *testing.T) {
|
||||
// good case: default version & deserialization
|
||||
setDeserialization := func(smtc *vaultTestCase) {
|
||||
smtc.apiOutput.SecretName = utilpointer.StringPtr(`{"foo":"bar"}`)
|
||||
smtc.expectedData["foo"] = []byte("bar")
|
||||
}
|
||||
|
||||
// bad case: invalid json
|
||||
setInvalidJSON := func(smtc *vaultTestCase) {
|
||||
smtc.apiOutput.SecretName = utilpointer.StringPtr(`-----------------`)
|
||||
smtc.expectError = "unable to unmarshal secret"
|
||||
}
|
||||
|
||||
successCases := []*vaultTestCase{
|
||||
makeValidVaultTestCaseCustom(setDeserialization),
|
||||
makeValidVaultTestCaseCustom(setInvalidJSON),
|
||||
makeValidVaultTestCaseCustom(setNilMockClient),
|
||||
makeValidVaultTestCaseCustom(setAPIErr),
|
||||
}
|
||||
|
||||
sm := VaultManagementService{}
|
||||
for k, v := range successCases {
|
||||
sm.Client = v.mockClient
|
||||
out, err := sm.GetSecretMap(context.Background(), *v.ref)
|
||||
if !ErrorContains(err, v.expectError) {
|
||||
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
|
||||
}
|
||||
if err == nil && !reflect.DeepEqual(out, v.expectedData) {
|
||||
t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, v.expectedData, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorContains(out error, want string) bool {
|
||||
if out == nil {
|
||||
return want == ""
|
||||
}
|
||||
if want == "" {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(out.Error(), want)
|
||||
}
|
|
@ -23,6 +23,7 @@ import (
|
|||
_ "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/gitlab"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/ibm"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/oracle"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/vault"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox"
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue