1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-14 11:57:59 +00:00

feat: update Pulumi provider for GA (#3917)

Signed-off-by: Engin Diri <engin.diri@ediri.de>
Co-authored-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
This commit is contained in:
Engin Diri 2024-09-21 09:54:12 +02:00 committed by GitHub
parent 680a3a4b8d
commit 231a6ea674
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 88 additions and 23 deletions

View file

@ -80,7 +80,7 @@ if settings.get('debug').get('enabled'):
docker_build_with_restart(
'ghcr.io/external-secrets/external-secrets',
'oci.external-secrets.io/external-secrets/external-secrets',
'.',
dockerfile = dockerfile,
entrypoint = entrypoint,

View file

@ -20,7 +20,7 @@ import (
type PulumiProvider struct {
// APIURL is the URL of the Pulumi API.
// +kubebuilder:default="https://api.pulumi.com/api/preview"
// +kubebuilder:default="https://api.pulumi.com/api/esc"
APIURL string `json:"apiUrl,omitempty"`
// AccessToken is the access tokens to sign in to the Pulumi Cloud Console.
@ -30,6 +30,8 @@ type PulumiProvider struct {
// To create a new organization, visit https://app.pulumi.com/ and click "New Organization".
Organization string `json:"organization"`
// Project is the name of the Pulumi ESC project the environment belongs to.
Project string `json:"project"`
// Environment are YAML documents composed of static key-value pairs, programmatic expressions,
// dynamically retrieved values from supported providers including all major clouds,
// and other Pulumi ESC environments.

View file

@ -3817,7 +3817,7 @@ spec:
type: object
type: object
apiUrl:
default: https://api.pulumi.com/api/preview
default: https://api.pulumi.com/api/esc
description: APIURL is the URL of the Pulumi API.
type: string
environment:
@ -3832,10 +3832,15 @@ spec:
Organization are a space to collaborate on shared projects and stacks.
To create a new organization, visit https://app.pulumi.com/ and click "New Organization".
type: string
project:
description: Project is the name of the Pulumi ESC project
the environment belongs to.
type: string
required:
- accessToken
- environment
- organization
- project
type: object
scaleway:
description: Scaleway

View file

@ -3817,7 +3817,7 @@ spec:
type: object
type: object
apiUrl:
default: https://api.pulumi.com/api/preview
default: https://api.pulumi.com/api/esc
description: APIURL is the URL of the Pulumi API.
type: string
environment:
@ -3832,10 +3832,15 @@ spec:
Organization are a space to collaborate on shared projects and stacks.
To create a new organization, visit https://app.pulumi.com/ and click "New Organization".
type: string
project:
description: Project is the name of the Pulumi ESC project
the environment belongs to.
type: string
required:
- accessToken
- environment
- organization
- project
type: object
scaleway:
description: Scaleway

View file

@ -4188,7 +4188,7 @@ spec:
type: object
type: object
apiUrl:
default: https://api.pulumi.com/api/preview
default: https://api.pulumi.com/api/esc
description: APIURL is the URL of the Pulumi API.
type: string
environment:
@ -4203,10 +4203,14 @@ spec:
Organization are a space to collaborate on shared projects and stacks.
To create a new organization, visit https://app.pulumi.com/ and click "New Organization".
type: string
project:
description: Project is the name of the Pulumi ESC project the environment belongs to.
type: string
required:
- accessToken
- environment
- organization
- project
type: object
scaleway:
description: Scaleway
@ -9968,7 +9972,7 @@ spec:
type: object
type: object
apiUrl:
default: https://api.pulumi.com/api/preview
default: https://api.pulumi.com/api/esc
description: APIURL is the URL of the Pulumi API.
type: string
environment:
@ -9983,10 +9987,14 @@ spec:
Organization are a space to collaborate on shared projects and stacks.
To create a new organization, visit https://app.pulumi.com/ and click "New Organization".
type: string
project:
description: Project is the name of the Pulumi ESC project the environment belongs to.
type: string
required:
- accessToken
- environment
- organization
- project
type: object
scaleway:
description: Scaleway

View file

@ -6010,6 +6010,17 @@ To create a new organization, visit <a href="https://app.pulumi.com/">https://ap
</tr>
<tr>
<td>
<code>project</code></br>
<em>
string
</em>
</td>
<td>
<p>Project is the name of the Pulumi ESC project the environment belongs to.</p>
</td>
</tr>
<tr>
<td>
<code>environment</code></br>
<em>
string

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

View file

@ -2,13 +2,17 @@
Sync environments, configs and secrets from [Pulumi ESC](https://www.pulumi.com/product/esc/) to Kubernetes using the External Secrets Operator.
![Pulumi ESC](../pictures/pulumi-esc.png)
More information about setting up [Pulumi](https://www.pulumi.com/) ESC can be found in the [Pulumi ESC documentation](https://www.pulumi.com/docs/esc/).
### Authentication
Pulumi [Access Tokens](https://www.pulumi.com/docs/pulumi-cloud/access-management/access-tokens/) are recommended to access Pulumi ESC.
### Creating a SecretStore
A Pulumi SecretStore can be created by specifying the `organization` and `environment` and referencing a Kubernetes secret containing the `accessToken`.
A Pulumi `SecretStore` can be created by specifying the `organization`, `project` and `environment` and referencing a Kubernetes secret containing the `accessToken`.
```yaml
apiVersion: external-secrets.io/v1beta1
@ -19,6 +23,7 @@ spec:
provider:
pulumi:
organization: <NAME_OF_THE_ORGANIZATION>
project: <NAME_OF_THE_PROJECT>
environment: <NAME_OF_THE_ENVIRONMENT>
accessToken:
secretRef:
@ -26,7 +31,29 @@ spec:
key: <KEY_IN_KUBE_SECRET>
```
If required, the API URL (`apiUrl`) can be customized as well. If not specified, the default value is `https://api.pulumi.com/api/preview`.
If required, the API URL (`apiUrl`) can be customized as well. If not specified, the default value is `https://api.pulumi.com/api/esc`.
### Creating a ClusterSecretStore
Similarly, a `ClusterSecretStore` can be created by specifying the `namespace` and referencing a Kubernetes secret containing the `accessToken`.
```yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: secret-store
spec:
provider:
pulumi:
organization: <NAME_OF_THE_ORGANIZATION>
project: <NAME_OF_THE_PROJECT>
environment: <NAME_OF_THE_ENVIRONMENT>
accessToken:
secretRef:
name: <NAME_OF_KUBE_SECRET>
key: <KEY_IN_KUBE_SECRET>
namespace: <NAMESPACE>
```
### Referencing Secrets

2
go.mod
View file

@ -89,7 +89,7 @@ require (
github.com/lestrrat-go/jwx/v2 v2.1.1
github.com/maxbrunsfeld/counterfeiter/v6 v6.9.0
github.com/passbolt/go-passbolt v0.7.1
github.com/pulumi/esc-sdk/sdk v0.9.2
github.com/pulumi/esc-sdk/sdk v0.10.0
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30
github.com/sethvargo/go-password v0.3.1
github.com/spf13/pflag v1.0.5

4
go.sum
View file

@ -596,8 +596,8 @@ github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJ
github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/pulumi/esc-sdk/sdk v0.9.2 h1:I+kKa7F/gY9lUiHEYuczHyrYB299CavG7rAB1yXybSw=
github.com/pulumi/esc-sdk/sdk v0.9.2/go.mod h1:J6+8bCUJyLXvYOmTAc90/EhU1iUPr1Koo3NUnFzY78k=
github.com/pulumi/esc-sdk/sdk v0.10.0 h1:tVZGVSVgSf/3UkKI3iC9E287eXw9VERvmdI4vN2BD4o=
github.com/pulumi/esc-sdk/sdk v0.10.0/go.mod h1:J6+8bCUJyLXvYOmTAc90/EhU1iUPr1Koo3NUnFzY78k=
github.com/r3labs/diff v0.0.0-20191120142937-b4ed99a31f5a h1:2v4Ipjxa3sh+xn6GvtgrMub2ci4ZLQMvTaYIba2lfdc=
github.com/r3labs/diff v0.0.0-20191120142937-b4ed99a31f5a/go.mod h1:ozniNEFS3j1qCwHKdvraMn1WJOsUxHd7lYfukEIS4cs=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=

View file

@ -39,6 +39,7 @@ const (
errNoStoreTypeOrWrongStoreType = "no store type or wrong store type"
errOrganizationIsRequired = "organization is required"
errEnvironmentIsRequired = "environment is required"
errProjectIsRequired = "project is required"
errSecretRefNameIsRequired = "secretRef.name is required"
errSecretRefKeyIsRequired = "secretRef.key is required"
)
@ -52,7 +53,6 @@ func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore,
if storeKind == esv1beta1.ClusterSecretStoreKind && doesConfigDependOnNamespace(cfg) {
return nil, errors.New(errClusterStoreRequiresNamespace)
}
accessToken, err := loadAccessTokenSecret(ctx, cfg.AccessToken, kube, storeKind, namespace)
if err != nil {
return nil, err
@ -69,6 +69,7 @@ func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore,
return &client{
escClient: *escClient,
authCtx: authCtx,
project: cfg.Project,
environment: cfg.Environment,
organization: cfg.Organization,
}, nil
@ -100,7 +101,7 @@ func getConfig(store esv1beta1.GenericStore) (*esv1beta1.PulumiProvider, error)
cfg := spec.Provider.Pulumi
if cfg.APIURL == "" {
cfg.APIURL = "https://api.pulumi.com/api/preview"
cfg.APIURL = "https://api.pulumi.com/api/esc"
}
if cfg.Organization == "" {
@ -109,6 +110,9 @@ func getConfig(store esv1beta1.GenericStore) (*esv1beta1.PulumiProvider, error)
if cfg.Environment == "" {
return nil, errors.New(errEnvironmentIsRequired)
}
if cfg.Project == "" {
return nil, errors.New(errProjectIsRequired)
}
err := validateStoreSecretRef(store, cfg.AccessToken)
if err != nil {
return nil, err

View file

@ -31,6 +31,7 @@ import (
type client struct {
escClient esc.EscClient
authCtx context.Context
project string
environment string
organization string
}
@ -49,12 +50,11 @@ const (
var _ esv1beta1.SecretsClient = &client{}
func (c *client) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
env, err := c.escClient.OpenEnvironment(c.authCtx, c.organization, c.environment)
env, err := c.escClient.OpenEnvironment(c.authCtx, c.organization, c.project, c.environment)
if err != nil {
return nil, err
}
value, _, err := c.escClient.ReadEnvironmentProperty(c.authCtx, c.organization, c.environment, env.GetId(), ref.Key)
value, _, err := c.escClient.ReadEnvironmentProperty(c.authCtx, c.organization, c.project, c.environment, env.GetId(), ref.Key)
if err != nil {
return nil, err
}
@ -97,7 +97,7 @@ func (c *client) PushSecret(_ context.Context, secret *corev1.Secret, data esv1b
},
},
}
_, oldValues, err := c.escClient.OpenAndReadEnvironment(c.authCtx, c.organization, c.environment)
_, oldValues, err := c.escClient.OpenAndReadEnvironment(c.authCtx, c.organization, c.project, c.environment)
if err != nil {
return fmt.Errorf(errReadEnvironment, err)
}
@ -105,7 +105,7 @@ func (c *client) PushSecret(_ context.Context, secret *corev1.Secret, data esv1b
if err := mergo.Merge(&updatePayload.Values.AdditionalProperties, oldValues); err != nil {
return fmt.Errorf(errPushSecrets, err)
}
_, err = c.escClient.UpdateEnvironment(c.authCtx, c.organization, c.environment, updatePayload)
_, err = c.escClient.UpdateEnvironment(c.authCtx, c.organization, c.environment, c.project, updatePayload)
if err != nil {
return fmt.Errorf(errPushSecrets, err)
}
@ -144,11 +144,11 @@ func GetMapFromInterface(i interface{}) (map[string][]byte, error) {
}
func (c *client) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
env, err := c.escClient.OpenEnvironment(c.authCtx, c.organization, c.environment)
env, err := c.escClient.OpenEnvironment(c.authCtx, c.organization, c.project, c.environment)
if err != nil {
return nil, err
}
value, _, err := c.escClient.ReadEnvironmentProperty(c.authCtx, c.organization, c.environment, env.GetId(), ref.Key)
value, _, err := c.escClient.ReadEnvironmentProperty(c.authCtx, c.organization, c.project, c.environment, env.GetId(), ref.Key)
if err != nil {
return nil, err
}

View file

@ -16,6 +16,7 @@ package pulumi
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
@ -38,7 +39,7 @@ func newTestClient(t *testing.T, _, pattern string, handler func(w http.Response
mux := http.NewServeMux()
mux.HandleFunc(pattern, handler)
mux.HandleFunc("/environments/foo/bar/open/", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/environments/foo/default/bar/open/", func(w http.ResponseWriter, r *http.Request) {
r.Header.Add(contentType, contentTypeValue)
w.Header().Add(contentType, contentTypeValue)
w.WriteHeader(http.StatusOK)
@ -65,6 +66,7 @@ func newTestClient(t *testing.T, _, pattern string, handler func(w http.Response
authCtx: ctx,
organization: "foo",
environment: "bar",
project: "default",
}
}
@ -73,7 +75,7 @@ func TestGetSecret(t *testing.T) {
"b": "world",
}
client := newTestClient(t, http.MethodGet, "/environments/foo/bar/open/session-id", func(w http.ResponseWriter, r *http.Request) {
client := newTestClient(t, http.MethodGet, "/environments/foo/default/bar/open/session-id", func(w http.ResponseWriter, r *http.Request) {
r.Header.Add(contentType, contentTypeValue)
w.Header().Add(contentType, contentTypeValue)
err := json.NewEncoder(w).Encode(esc.NewValue(testmap, esc.Trace{}))
@ -342,13 +344,14 @@ func TestGetSecretMap(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := newTestClient(t, http.MethodGet, "/environments/foo/bar/open/session-id", func(w http.ResponseWriter, r *http.Request) {
p := newTestClient(t, http.MethodGet, "/environments/foo/default/bar/open/session-id", func(w http.ResponseWriter, r *http.Request) {
r.Header.Add(contentType, contentTypeValue)
w.Header().Add(contentType, contentTypeValue)
err2 := json.NewEncoder(w).Encode(esc.NewValue(tt.input, esc.Trace{}))
require.NoError(t, err2)
})
got, err := p.GetSecretMap(context.TODO(), tt.ref)
fmt.Print(got)
if (err != nil) != tt.wantErr {
t.Errorf("ProviderPulumi.GetSecretMap() error = %v, wantErr %v", err, tt.wantErr)
return