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

WIP: heroku_app datasource #1

Merged
merged 2 commits into from
Apr 30, 2019
Merged

WIP: heroku_app datasource #1

merged 2 commits into from
Apr 30, 2019

Conversation

sheax0r
Copy link
Owner

@sheax0r sheax0r commented Feb 6, 2018

This pull request is intended to enable heroku_app data objects in terraform, like this:

data "heroku_app" "my_app" {
  name = "my-app"
}

This object exposes the following attributes:

  • name
  • space
  • organization
  • region
  • stack
  • buildpacks
  • config_vars
  • git_url
  • web_url
  • heroku_hostname

@sheax0r sheax0r force-pushed the heroku-app-datasource branch from fb77166 to 1745ddf Compare February 6, 2018 05:33
func dataSourceHerokuApp() *schema.Resource {
return &schema.Resource{
Read: dataSourceHerokuAppRead,
Schema: map[string]*schema.Schema{
Copy link

Choose a reason for hiding this comment

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

Please be sure these are documented somewhere.

Choose a reason for hiding this comment

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

I don't have experience with data sources, but I wonder if it would be useful to re-use the existing app resource schema here? Something like Schema: resourceHerokuApp().Schema?

For that matter, could you use resourceHerokuAppRead as the reader here to also re-use all of that logic?

Copy link
Owner Author

Choose a reason for hiding this comment

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

@bernerdschaefer I'll try to DRY this up later on, but I'm not sure how much I can do regarding schema reuse. The schema actually is subtly different - for instance, elements tend to be "required" in the resource schema, but "computed" in the datasource schema.

I'm using aws resources and datasources for reference (this is also my first experience writing datasources, or any terraform go code).

Choose a reason for hiding this comment

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

Ah, interesting. The repetition in read bothers me more than in the schema since there's rather a lot going on, but neither bothers me so badly that we couldn't ship this as a first pass.

Copy link
Owner Author

Choose a reason for hiding this comment

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

@bernerdschaefer I've refactored a bunch of the logic around retrieving data for an app, so that I can reuse stuff from resource_heroku_app inside of datasource_heroku_app.

@Xe I've added docs on datasource_heroku_app, using the existing heroku_app resource docs plus some aws datasource docs as a template.

It also now retrieves a bunch more stuff than the first pass did - buildpacks, config vars, organization info.

I still need to manually test organization and space functionality (so far I've only been testing with a common runtime app), and I need to write some tests.

@jmervine
Copy link
Collaborator

jmervine commented Feb 6, 2018

We should look at adding support for drains while we’re in here. If it isn’t already here somewhere.

@sheax0r
Copy link
Owner Author

sheax0r commented Feb 6, 2018

So far all this does is a subset of heroku_app. We should make our data objects correspond to the resource objects.

A drain is its own resource (see here); by all means add it. I feel like we should just try to add as much stuff as we can do (reasonably well) within the hackathon timebox.

Copy link

@bernerdschaefer bernerdschaefer left a comment

Choose a reason for hiding this comment

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

I can see some great uses for these data sources!

func dataSourceHerokuApp() *schema.Resource {
return &schema.Resource{
Read: dataSourceHerokuAppRead,
Schema: map[string]*schema.Schema{

Choose a reason for hiding this comment

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

I don't have experience with data sources, but I wonder if it would be useful to re-use the existing app resource schema here? Something like Schema: resourceHerokuApp().Schema?

For that matter, could you use resourceHerokuAppRead as the reader here to also re-use all of that logic?

return err
}

d.SetId(time.Now().UTC().String())

Choose a reason for hiding this comment

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

Interesting. Did you pick this up from how other data sources work?

Copy link
Owner Author

Choose a reason for hiding this comment

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

@bernerdschaefer I did - I got it from an AWS data source. acm_certificate, I think.

Copy link
Collaborator

@jmervine jmervine Feb 6, 2018

Choose a reason for hiding this comment

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

Consider d.SetId("hk-"+time.Now().UTC().String())?

Copy link
Owner Author

Choose a reason for hiding this comment

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

@jmervine are we concerned that there is some sort of potential negative effect from doing it this way, like collision of IDs? I literally copied this from the code for aws_acm_certificate, and I assumed (perhaps incorrectly) that they know what they're doing.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nah, I was just thinking about AWS does it, e.g. instances have i-<id>. But I defer to you. It was just a thought.

{"name": app.Organization.Name},
})
} else {
d.Set("organization", "")
Copy link
Collaborator

@jmervine jmervine Feb 6, 2018

Choose a reason for hiding this comment

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

Should this be an empty map, or perhaps nil when not present, or does an empty string work and make sense here?

Copy link
Owner Author

Choose a reason for hiding this comment

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

@jmervine so far in my testing - there are problems with using nil. Basically, I've got this terraform script:

data "heroku_app" "parent" {
  name = "${var.parent_app}"
}

resource "heroku_app" "child" {
  name = "${var.child_app}"
  region = "us-east"

  config_vars {
    ORGANIZATION = "${data.heroku_app.parent.organization}"
    REGION = "${data.heroku_app.parent.region}"
    SPACE = "${data.heroku_app.parent.space}"
    STACK = "${data.heroku_app.parent.stack}"
    GIT_URL = "${data.heroku_app.parent.git_url}"
    WEB_URL = "${data.heroku_app.parent.web_url}"
  }
}

I finding that if organization (or any other attribute) is nil, the terraform script will fail to apply, with errors like this:

* heroku_app.child: Resource 'data.heroku_app.parent' does not have attribute 'space' for variable 'data.heroku_app.parent.space'

So the question here is: Should the script actually fail if the app doesn't have an organization? My belief is "no". I think it's better to return an empty string.

I see three possiblities:

  1. I'm doing something wrong and it is possible to have a nil value for an attribute but still have attribute work if referenced (so far, though, I don't think I'm wrong about this).
  2. You actually shouldn't try to reference an attribute that is nil (this seems silly though - the script becomes very brittle if I want to apply the same recipe to different resources where some of them have an organization but others don't).
  3. What I'm doing in the script is correct, as is the way I'm continuing to expose "nil" attributes as empty strings.

BTW it looks like "how to represent nil attributes in terraform" is
a problem others have encountered: hashicorp/terraform#5471 (comment)

Copy link
Collaborator

@jmervine jmervine Feb 6, 2018

Choose a reason for hiding this comment

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

I tend to agree. Origination is optional and should be treated as such. The terraform pattern is typically to omit the config, if it's not required and not present. AFAIK, if most of the AWS resources, there is no way of saying "nil so fall back on default", if you set it to something, then it errors or applies what you set it to, even if that's an empty string.

That doesn't innately allow for reuse, unfortunately.

Copy link
Collaborator

@jmervine jmervine Feb 6, 2018

Choose a reason for hiding this comment

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

So if I'm understanding you correctly, if you don't handle the nil, it errors. How does the heroku lib handle the empty string? If the heroku lib handles an empty string, I'd go that route... I'd also be surprised if it does.

My other suggestion was to set it to an empty map, e.g.

d.Set("organization", []map[string]interface{}{})

Might that be better?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Err wait. What are you agreeing with? There were three possibilities, you can't agree with all of them. I'm not sure what you mean by "origination is optional and should be treated as such".

Based on the rest of your comment, I think you're agreeing with option 2.

Copy link
Owner Author

Choose a reason for hiding this comment

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

@jmervine it looks like, for consistency, option 2 is appropriate. Here's what happens if you try to reference "organization" for a heroku_app resource that doesn't have one:


* heroku_app.child: Resource 'heroku_app.managed' does not have attribute 'organization' for variable 'heroku_app.managed.organization'

... This is based on this terraform script:

data "heroku_app" "parent" {
  name = "${var.parent_app}"
}

resource "heroku_app" "managed" {
  name = "shea-managed-1"
  region = "us-east"
}

resource "heroku_app" "child" {
  name = "${var.child_app}"
  region = "us-east"

  config_vars {
    ORGANIZATION = "${data.heroku_app.parent.organization}"
    REGION = "${data.heroku_app.parent.region}"
    SPACE = "${data.heroku_app.parent.space}"
    STACK = "${data.heroku_app.parent.stack}"
    GIT_URL = "${data.heroku_app.parent.git_url}"
    WEB_URL = "${data.heroku_app.parent.web_url}"

    MANAGED_ORGANIZATION = "${heroku_app.managed.organization}"
  }
}

Copy link
Owner Author

Choose a reason for hiding this comment

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

So, I'm going to simply fail if users try to reference nil attributes. I think this is a shortcoming of terraform, but it's not one that is really possible to work around in any good way at this point.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there even possible to pass a nil value via Terraform? I thought it was omit the config or set it to a string or map. Are empty maps (or blocks in terraform DSL) even possible?

I was agreeing with 'Should the script actually fail if the app doesn't have an organization? My belief is "no".'

sidebar_current: "docs-heroku-datasource-app-x"
description: |-
Get information on a Heroku App.
---
Copy link

Choose a reason for hiding this comment

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

👍 👍 👍

@@ -30,8 +30,8 @@ The following arguments are supported:

The following attributes are exported:

* `name` - (Required) The name of the application. In Heroku, this is also the
unique ID.
* `name` - (Required) The nam of the application. In Heroku, this is also the
Copy link

Choose a reason for hiding this comment

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

name

@@ -41,10 +41,6 @@ The following arguments are supported:
* `buildpacks` - (Optional) Buildpack names or URLs for the application.
Buildpacks configured externally won't be altered if this is not present.
* `config_vars` - (Optional) Configuration variables for the application.
The config variables in this map are not the final set of configuration
Copy link
Collaborator

Choose a reason for hiding this comment

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

@sheax0r Are you removing this comment because the functionality has changed?

Copy link
Owner Author

Choose a reason for hiding this comment

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

@jmervine I'm removing it because it's incorrect; the removed text was copied from the heroku_app resource, where it applies - but the datasource is slightly different.

The data object exposes all config vars in its config_vars attribute.

The resource exposes only certain config vars in its config_vars attribute - ie, ones that the user has decided to care about.

The datasource and the resource are necessarily different in this respect.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Oh wait @jmervine I see what you're saying now! I removed this text from the resource, not the datasource! :D.

My bad. I will put it back.

@@ -30,7 +30,7 @@ The following arguments are supported:

The following attributes are exported:

* `name` - (Required) The nam of the application. In Heroku, this is also the
* `name` - (Required) The name of the application. In Heroku, this is also the
Copy link

Choose a reason for hiding this comment

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

🎉

@sheax0r sheax0r force-pushed the heroku-app-datasource branch from cddd3da to 735fc12 Compare February 12, 2018 01:14
@sheax0r sheax0r merged commit cced324 into master Apr 30, 2019
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.

4 participants