Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle additionalProperties #963

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open

Conversation

praneetloke
Copy link

@praneetloke praneetloke commented Feb 14, 2024

This should fix #401.

As stated in the linked issue, the type for the stateful policy of a managed instance group in the compute module should be a mapping of string and an object that accepts the value for the auto-delete rule (enum). The possible values for the auto-delete rule are NEVER and ON_PERMANENT_INSTANCE_DELETION.

I looked into this briefly and found that the discovery doc for compute.v1 has the right mapping but my guess is that when generating the schema, the generator is dropping the $ref on the additionalProperties which turns these into a simple mapping instead of a map of maps.

Here's a snippet from the discovery doc on the master branch:

"StatefulPolicyPreservedState": {
      "description": "Configuration of preserved resources.",
      "id": "StatefulPolicyPreservedState",
      "properties": {
        "disks": {
          "additionalProperties": {
            "$ref": "StatefulPolicyPreservedStateDiskDevice"
          },
          "description": "Disks created on the instances that will be preserved on instance delete, update, etc. This map is keyed with the device names of the disks.",
          "type": "object"
        },
        "externalIPs": {
          "additionalProperties": {
            "$ref": "StatefulPolicyPreservedStateNetworkIp"
          },
          "description": "External network IPs assigned to the instances that will be preserved on instance delete, update, etc. This map is keyed with the network interface name.",
          "type": "object"
        },
        "internalIPs": {
          "additionalProperties": {
            "$ref": "StatefulPolicyPreservedStateNetworkIp"
          },
          "description": "Internal network IPs assigned to the instances that will be preserved on instance delete, update, etc. This map is keyed with the network interface name.",
          "type": "object"
        }
      },
      "type": "object"
    },

And here's the Pulumi schema for v1:

"google-native:compute/v1:StatefulPolicy": {
            "properties": {
                "preservedState": {
                    "type": "object",
                    "$ref": "#/types/google-native:compute/v1:StatefulPolicyPreservedState"
                }
            },
            "type": "object"
        },
        "google-native:compute/v1:StatefulPolicyPreservedState": {
            "description": "Configuration of preserved resources.",
            "properties": {
                "disks": {
                    "type": "object",
                    "description": "Disks created on the instances that will be preserved on instance delete, update, etc. This map is keyed with the device names of the disks."
                },
                "externalIPs": {
                    "type": "object",
                    "description": "External network IPs assigned to the instances that will be preserved on instance delete, update, etc. This map is keyed with the network interface name."
                },
                "internalIPs": {
                    "type": "object",
                    "description": "Internal network IPs assigned to the instances that will be preserved on instance delete, update, etc. This map is keyed with the network interface name."
                }
            },
            "type": "object"
        },

Note how all properties of StatefulPolicyPreservedState are missing the additionalProperties definition. (I guess the code generator is defaulting properties of type: object without any other attributes as maps?)

This PR handles additionalProperties for both cases where a property is a map with a primitive value or one where the value is a reference to a type.

Copy link

PR is now waiting for a maintainer to run the acceptance tests.
Note for the maintainer: To run the acceptance tests, please comment /run-acceptance-tests on the PR

return &schema.TypeSpec{
Type: "object",
AdditionalProperties: &schema.TypeSpec{
Type: "string",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be hard-coded to string. Ideally, this uses prop.AdditionalProperties.Type but that leads to some errors that I didn't look deeply into. But then again, perhaps a string is ok here for now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's true that these are usually strings, but I think if you instead recurse on prop.AditionalProperties, you can get the full type.

@praneetloke
Copy link
Author

Ping.

Copy link
Contributor

@mjeffryes mjeffryes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First off: thanks for contributing this PR! And apologies that it's taken us so long to review.

Conceptually, I think this is about right, but there are a couple nuances I'd like to make sure we get correct. And you'll also need to run a full make build to regenerate all the sdks before we can run tests.

FWIW, we recently made a similar change in the generator for the aws-native cloud control provider; you might look at this part for some inspiration/comparison: https://github.com/pulumi/pulumi-aws-native/pull/1342/files#diff-a462bb9d1699adb657844ae5c68deb17b7edfd49a2a39ab28f7f53e7e8a380b7

return &schema.TypeSpec{
Type: "object",
AdditionalProperties: &schema.TypeSpec{
Type: "string",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's true that these are usually strings, but I think if you instead recurse on prop.AditionalProperties, you can get the full type.


// Otherwise, the value in-turn is a complex type.
typePropName := fmt.Sprintf(`%s%s`, typeName, strings.Title(propName))
return g.genTypeSpec(typePropName, propName, prop.AdditionalProperties, isOutput)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this ends up just return the type of the value rather than a map from string -> valueType

@mikhailshilkov
Copy link
Member

@praneetloke Are you still planning to address Matt's suggestions here?

@praneetloke
Copy link
Author

@mikhailshilkov I am going to try to get to this this week. If not, I'll close it. I really think this should be finished though, to improve the quality of this native provider. As it stands right now, the bug this is trying to fix corrects a lot of inaccuracies in the schema as you can see in the diff.

Copy link

PR is now waiting for a maintainer to run the acceptance tests.
Note for the maintainer: To run the acceptance tests, please comment /run-acceptance-tests on the PR

Copy link

PR is now waiting for a maintainer to run the acceptance tests.
Note for the maintainer: To run the acceptance tests, please comment /run-acceptance-tests on the PR

Copy link

PR is now waiting for a maintainer to run the acceptance tests.
Note for the maintainer: To run the acceptance tests, please comment /run-acceptance-tests on the PR

Copy link

PR is now waiting for a maintainer to run the acceptance tests.
Note for the maintainer: To run the acceptance tests, please comment /run-acceptance-tests on the PR

// AdditionalProperties type spec.
//
// Discovery doc: https://servicemanagement.googleapis.com/$discovery/rest?version=v1
"overridesByRequestProtocol": {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully, the above comment is self-explanatory. If you have a different approach to fixing this or think that the recursive type is actually valid in this case, please let me know and I can revert this. However, that would mean that a solution for the recursive reference would be needed for this provider's schema (perhaps similar to k8s' ObjectMeta?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I discovered this issue when I ran make build. Here's how the property is defined in the discovery doc:

"BackendRule": {
...
...
    "overridesByRequestProtocol": {
        "additionalProperties": {
          "$ref": "BackendRule"
        },
        "description": "The map between request protocol and the backend address.",
        "type": "object"
     },
...
...
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reading of that schema is that it's probably supposed to be recursive-ish. The field name suggests that you could override any of the values in BackendRule for a particular protocol. It's probably nonsensical to have an "overridesByRequestProtocol" within an "overridesByRequestProtocol" parameter. However, I suspect a string map won't work, because it's probably expecting a map of objects object instead of a map of strings.

The safest thing to do would probably be to omit this property entirely until someone can take the time to explore the behavior of this api

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reading of that schema is that it's probably supposed to be recursive-ish. The field name suggests that you could override any of the values in BackendRule for a particular protocol.

It's even more strange than that. From looking at the APIs you can't create a backend rule. This is just used in the response to retrieve (get) a service config. So I don't know how this is used and the documentation for the field is conflicting with how the type is represented. Having said that, I kind of agree with you that this is probably supposed to be recursive without the overridesByRequestProtocol being present in the recursed types.

I can exclude the property entirely for now instead of doing this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The safest thing to do would probably be to omit this property entirely until someone can take the time to explore the behavior of this api

This is done.

@praneetloke
Copy link
Author

@mjeffryes @mikhailshilkov I've updated this PR. I've separated the SDK regen as a separate commit[1]. I think I have addressed the comments. I also uncovered a problem in the discovery doc for servicemanagement. Please have a look at that.

[1]=It's a large number of changes. But let's be honest, this is not the largest number of changes I've made in a PR 😄

Copy link

PR is now waiting for a maintainer to run the acceptance tests.
Note for the maintainer: To run the acceptance tests, please comment /run-acceptance-tests on the PR

Copy link

github-actions bot commented May 3, 2024

PR is now waiting for a maintainer to run the acceptance tests.
Note for the maintainer: To run the acceptance tests, please comment /run-acceptance-tests on the PR

Copy link
Contributor

@mjeffryes mjeffryes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few comments on some edge cases, but I think this is pretty close. I spot checked some of the schema changes and it definitely looks like an improvement. We'll also need some tests for the new behavior to land this.

// AdditionalProperties type spec.
//
// Discovery doc: https://servicemanagement.googleapis.com/$discovery/rest?version=v1
"overridesByRequestProtocol": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reading of that schema is that it's probably supposed to be recursive-ish. The field name suggests that you could override any of the values in BackendRule for a particular protocol. It's probably nonsensical to have an "overridesByRequestProtocol" within an "overridesByRequestProtocol" parameter. However, I suspect a string map won't work, because it's probably expecting a map of objects object instead of a map of strings.

The safest thing to do would probably be to omit this property entirely until someone can take the time to explore the behavior of this api

Comment on lines +1423 to +1430
default:
return &schema.TypeSpec{
Type: "object",
AdditionalProperties: &schema.TypeSpec{
Type: prop.AdditionalProperties.Type,
},
}, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for "boolean", "number", "integer", & "string"; however, we should exclude "null" and "object" types from this. ("null" is not a valid pulumi type and "object" does not make sense without any parameters)

Comment on lines 1410 to 1413
// An array type for AdditionalProperties doesn't make sense
// since it's already a map and multiple entries are
// allowed by definition. So generate a type
// based on the type of the "items" in the array.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree, a map[string][]T is a perfectly valid type (eg. you might have a map from team name to employees list).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a map[string][]T is a perfectly valid type (eg. you might have a map from team name to employees list).

Yes. But I wasn't referring to that :) My confusion was about type: array and what that is referring to. I guess I didn't realize that the type: array here is indicating a list of values for each key. If that is the case, then yeah what I have here is definitely wrong. I'll change this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed this now.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, the reason why the array type didn't make sense to me in the first place in this context is, if you look at the UI for creating a budget, you can only have a single value for a key:
image

Having said that I agree that this can't be generalized like how I did at first because there could be a <string, any[]> elsewhere in the future that this would break.

@mjeffryes
Copy link
Contributor

/run-acceptance-tests

@pulumi-bot
Copy link
Contributor

Copy link

github-actions bot commented May 3, 2024

PR is now waiting for a maintainer to run the acceptance tests.
Note for the maintainer: To run the acceptance tests, please comment /run-acceptance-tests on the PR

@praneetloke praneetloke requested a review from mjeffryes May 3, 2024 19:43
@mjeffryes
Copy link
Contributor

mjeffryes commented May 14, 2024

Thanks for updating this @praneetloke; I think we still need to address #963 (comment) and add some test cases to wrap this up. If I get a chance I will try to see if I can add these, but it may be a while so you might beat me to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Stateful Instance Group policy external/internal ips accepts Mapping[str, str] and not str
4 participants