EventStore / pulumi-eventstorecloud

Pulumi provider for Event Store Cloud
Apache License 2.0
2 stars 5 forks source link

ProviderArgs are not optional and can't be left as empty string #19

Open diegosasw opened 1 year ago

diegosasw commented 1 year ago

I need to use explicit organizationId + token without relying on environment variables. So I would like to explicitly create a Provider associated to Event Store Cloud which I can later use to explicitly associate to some resources (e.g: the event store cloud project, cluster, etc.)

The problem is that there are no optional ProviderArgs despite the documentation suggesting so See https://github.com/EventStore/pulumi-eventstorecloud/blob/c0f3bfbd31561c88497e055ab8d033da6cbcdac0/sdk/nodejs/provider.ts#L79 and https://developers.eventstore.com/cloud/automation/#provider-configuration

Which are the default clientId, tokenStore, identityProviderUrl and url I should provide?

    const eventStoreDbProvider = new eventstore.Provider(
      `${args.prefix}-eventstoredb-provider`,
      {
        token: args.eventStoreDbToken,
        organizationId: args.eventStoreDbOrganizationId,
        clientId: "",
        tokenStore: "",
        identityProviderUrl: "",
        url: ""
      },
      {
        parent: this
      }
    );

The above would cause

error: Could not automatically download and install resource plugin 'pulumi-resource-eventstorecloud' at version v0.2.8, install the plugin using pulumi plugin install resource eventstorecloud v0.2.8 --server https://github.com/EventStore/pulumi-eventstorecloud/releases/download/0.2.8: error downloading provider eventstorecloud to file: failed to download plugin: eventstorecloud-0.2.8: 404 HTTP error fetching plugin from https://github.com/EventStore/pulumi-eventstorecloud/releases/download/0.2.8/pulumi-resource-eventstorecloud-v0.2.8-windows-amd64.tar.gz

but I am not using .NET SDK nor Go, but Typescript/NodeJs and the documentation says

For projects that use .NET and Go Pulumi SDK you have to install the provider before trying to update the stack.

Is this also needed for Typescript?

CLO-434

diegosasw commented 1 year ago

I found this https://github.com/EventStore/terraform-provider-eventstorecloud/blob/63704ce71c909347fa1012a2d046cd706a7dd0ac/client/client.go#L72

which seems to imply that the identityProviderUrl is https://identity.eventstore.com (https://identity.eventstore.com/.well-known/openid-configuration is available) and has some default hardcoded valuesif clientId is empty.

But still don't know what to put under tokenStore and url, which are mandatory as per

export interface ProviderArgs {
    clientId: pulumi.Input<string>;
    identityProviderUrl: pulumi.Input<string>;
    organizationId: pulumi.Input<string>;
    token: pulumi.Input<string>;
    tokenStore: pulumi.Input<string>;
    url: pulumi.Input<string>;
}

Any sample using the Pulumi's event store cloud provider would be much appreciated

PS: Today I was unable to test it as event store cloud provisioning seems to be disable for up to 24-48 hours until they review a request form.

alexeyzimarev commented 1 year ago

Check this https://github.com/EventStore/terraform-provider-eventstorecloud/blob/0e216bcb18ba60ac28eed471ceac5150cf4bb094/client/client.go#L70-L82

These can be empty strings, and the TF provider will set them.

diegosasw commented 1 year ago

url cannot be empty string.

I am getting

eventstorecloud:index:Project (my-eventstore):
    error: 1 error occurred:
        * URL is required

when creating

const project = new esc.Project(
  "my-eventstore",
  {
    name: "my-project"
  },
  { parent: this, provider: eventStoreProvider }
);

Which is expected as per url validation https://github.com/EventStore/terraform-provider-eventstorecloud/blob/0e216bcb18ba60ac28eed471ceac5150cf4bb094/client/client.go#L23

The event store provider is

// Empty optional values as per https://github.com/EventStore/pulumi-eventstorecloud/issues/19
const eventStoreProvider  = new eventstore.Provider(
  "my-provider",
  {
    token: args.eventStoreDbToken, // this is read from config secret eventstorecloud:token
    organizationId: args.eventStoreDbOrganizationId, // this is read from config (not a secret) eventstorecloud:organizationId
    clientId: "",
    tokenStore: "",
    identityProviderUrl: "",
    url: ""
  },
  {
    parent: this
  }
);

If I pass any random url value

eventstorecloud:index:Project (my-eventstore):
    error: 1 error occurred:
        * cannot create path "": mkdir : The system cannot find the path specified.

Which URL is this supposed to be?

An empty tokenStore may also cause problems as per https://github.com/EventStore/terraform-provider-eventstorecloud/blob/0e216bcb18ba60ac28eed471ceac5150cf4bb094/client/client.go#L28

alexeyzimarev commented 1 year ago

If it's a bug, it's the generator bug. These values are optional in Terraform as far as I can see. You can find the URL in the Terraform provider code, which I posted earlier. Same for the client id:

    identityProviderURL := opts.IdentityProviderURL
    if strings.TrimSpace(identityProviderURL) == "" {
        identityProviderURL = "https://identity.eventstore.com"
    }
    parsedIdentityProviderURL, err := url.Parse(identityProviderURL)
    if err != nil {
        return nil, fmt.Errorf("invalid identity provider URL: %q, %w", identityProviderURL, err)
    }

    clientID := opts.ClientID
    if strings.TrimSpace(clientID) == "" {
        clientID = "OraYp3cFES9O8aWuQtnqi1A7m534iTwt"
    }

Token store is basically the path where the token will be stored on disk.

diegosasw commented 1 year ago

That's the identityProviderUrl, not the url, which I don't even know what is it or why is needed. Same as the tokenStore. See below terraform requiring a value for url

func (config *Config) validate() error {
    if strings.TrimSpace(config.URL) == "" {
        return errors.New("URL is required")
    }

    if _, err := os.Stat(config.TokenStore); err != nil {
        if os.IsNotExist(err) {
            err := os.MkdirAll(config.TokenStore, 0700)
            if err != nil {
                return fmt.Errorf("cannot create path %q: %w", config.TokenStore, err)
            }

            return nil
        }

        return fmt.Errorf("error reading Token Store %q: %w", config.TokenStore, err)
    }

    return nil
}
export interface ProviderArgs {
    clientId: pulumi.Input<string>;
    identityProviderUrl: pulumi.Input<string>;
    organizationId: pulumi.Input<string>;
    token: pulumi.Input<string>;
    tokenStore: pulumi.Input<string>; // What's this?
    url: pulumi.Input<string>; // What's this?
}
alexeyzimarev commented 1 year ago

As I mentioned before, the token store is the location on disk where the refresh token would be stored locally.

The url parameter is the URL of the ES Cloud API. If it defined in Terraform provider like this, so it is required, but it has a default value:

"url": {
    Type:        schema.TypeString,
    Required:    true,
    DefaultFunc: schema.EnvDefaultFunc("ESC_URL", "https://api.eventstore.cloud"),
},
alexeyzimarev commented 1 year ago

Here's the default token store:

defaultTokenStore = filepath.Join(os.Getenv("HOME"), ".esctf", "tokens")
alexeyzimarev commented 1 year ago

Btw, the TypeScript code for provider indeed has this issue, and, I believe, it's the Pulumi generator issue. I know it's a bad excuse, but here how the .NET SDK code looks like:

var merged = CustomResourceOptions.Merge(defaultOptions, options);

There are no pre-checks there. And here's the TypeScript code:

        opts = opts || {};
        {
            if ((!args || args.clientId === undefined) && !opts.urn) {
                throw new Error("Missing required property 'clientId'");
            }
            if ((!args || args.identityProviderUrl === undefined) && !opts.urn) {
                throw new Error("Missing required property 'identityProviderUrl'");
            }
            if ((!args || args.organizationId === undefined) && !opts.urn) {
                throw new Error("Missing required property 'organizationId'");
            }
            if ((!args || args.token === undefined) && !opts.urn) {
                throw new Error("Missing required property 'token'");
            }
            if ((!args || args.tokenStore === undefined) && !opts.urn) {
                throw new Error("Missing required property 'tokenStore'");
            }
            if ((!args || args.url === undefined) && !opts.urn) {
                throw new Error("Missing required property 'url'");
            }
            resourceInputs["clientId"] = args ? args.clientId : undefined;
            resourceInputs["identityProviderUrl"] = args ? args.identityProviderUrl : undefined;
            resourceInputs["organizationId"] = args ? args.organizationId : undefined;
            resourceInputs["token"] = args ? args.token : undefined;
            resourceInputs["tokenStore"] = args ? args.tokenStore : undefined;
            resourceInputs["url"] = args ? args.url : undefined;
        }
        opts = pulumi.mergeOptions(utilities.resourceOptsDefaults(), opts);

You can see that the first check the options, and then do the merge. It should be in reverse.

diegosasw commented 1 year ago

Thank you. The workaround with explicit values works well

const eventStoreDbProvider = new eventstore.Provider(
  `foo`,
  {
    token: args.eventStoreDbToken,
    organizationId: args.eventStoreDbOrganizationId,
    clientId: "OraYp3cFES9O8aWuQtnqi1A7m534iTwt", // explicit, should be able to leave it blank
    tokenStore: path.join(os.homedir(), ".esctf", "tokens"), // explicit, should be able to leave it blank
    identityProviderUrl: "https://identity.eventstore.com", // explicit, should be able to leave it blank
    url: "https://api.eventstore.cloud" // explicit, should be able to leave it blank
  },
  {
    parent: this
  }
);