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

New resource: Automation Account/Credential/Runbook/Schedule #257

Merged

Conversation

Kemyke
Copy link
Contributor

@Kemyke Kemyke commented Aug 18, 2017

This PR contains the minimal required resources to create and schedule an automated runbook. The automation account is the container of the credential, runbook, and schedule.
You can set the content URI for your runbook to create from. I couldn't find any way to create runbook from inline script.

Currently only OneTime schedule is creatable due to this bug in azure rest api spec (Azure/azure-rest-api-specs#1545). The Interval field cannot be set in the Go SDK so recurring schedule cannot be constructed.

The StartTime is a tricky field in the schedule because only future time is acceptable (at least 5 minutes from now). It is only control the first run of the job, the next runs will occure regarding of the interval. So i didn't use the StartTime directly in the terraform schema but i introduced a first_run object where you can set when to run first. This way you don't have to set an exact date but you can control in which hour/minute/day/... you want the job to run first. The script will calculate the first possible start time from these fields.

I will attach the documentation soon where i will show you examples. But first i want to show the code to review. Pushed.

@Kemyke Kemyke changed the title Automation resources New resource: Automation Account/Credential/Runbook/Schedule Aug 18, 2017
@tombuildsstuff tombuildsstuff self-assigned this Aug 30, 2017
Copy link
Contributor

@tombuildsstuff tombuildsstuff left a comment

Choose a reason for hiding this comment

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

Hey @Kemyke

Thanks for opening this PR and keeping up to date with all the recent changes - apologies for the delay in taking a look at this! I've taken a look through and left some comments in-line but this is off to a good start :)

The main question I've got is around the first_run field - I'm wondering if it'd make more sense as a data source, seeing as this could be reused in the Autoscale Settings fields too?

Thanks!

"github.com/hashicorp/terraform/helper/resource"
)

func TestAccAzureRMAutomationAccount_importAccoutWithFreeSku(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

minor can we make Accout -> Account?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

})
}

func TestAccAzureRMAutomationAccount_importAccoutWithBasicSku(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

minor can we make Accout -> Account?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

ForceNew: true,
},

"location": {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we swap this out for "location": locationSchema()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

},

"sku": {
Type: schema.TypeSet,
Copy link
Contributor

Choose a reason for hiding this comment

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

for newer resources we're converting things overt using List's instead of Set's - could we update this to using a List? That involves updating the type here to TypeList, removing the Set here and then updating the Expand/Flatten to use a List, as in this resource

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Sku: &sku,
},

Name: &name,
Copy link
Contributor

Choose a reason for hiding this comment

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

we should be able to remove the name field here, since it's specified in the CreateOrUpdate method below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


* `password` - (Required) The password of the Credential.

* `description` - (Optional) A description for this Credential.
Copy link
Contributor

Choose a reason for hiding this comment

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

minor could we phrase this as The username associated with this Automation Credential to match the style of the other resources?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. You mean The description associated... i guess :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, copy and paste fail 🤦‍♂️


* `account_name` - (Required) The name of the automation account in which the Runbook is created. Changing this forces a new resource to be created.

* `runbook_type` - (Required) The type of the runbook - can be either`Graph`, `GraphPowerShell`, `GraphPowerShellWorkflow`, `PowerShellWorkflow`, `PowerShell` or `Script`.
Copy link
Contributor

Choose a reason for hiding this comment

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

minor can we add a space between either and "Graph" here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

return fmt.Errorf("Cannot parse start_time: %s", cst)
}
} else {
starttime = computeValidStartTime(d.Get("first_run").(*schema.Set), freq)
Copy link
Contributor

Choose a reason for hiding this comment

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

given this is going to be needed for a few resources - would this make more sense as a datasource? I have a feeling that in it's current form this will cause a perpetual diff?

//interval :=

description := d.Get("description").(string)
timezone := "UTC"
Copy link
Contributor

Choose a reason for hiding this comment

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

can we make this configurable by the user, defaulted to UTC?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, docomentation is also updated


* `frequency` - (Required) The frequency of the schedule. - can be either `OneTime`, `Day`, `Hour`, `Week`, or `Month`.

* `first_run` - (Optional) If an exact start time is not suitable, it can be used to make constraints for the first run. The start time will be calculated depending on these constraints. `start_time` will override this settings if defined.
Copy link
Contributor

Choose a reason for hiding this comment

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

(as above) would this make more sense as a data source? 🤔

@Kemyke
Copy link
Contributor Author

Kemyke commented Aug 31, 2017

Hi @tombuildsstuff

first of all, thank you for your review. I fixed the easy ones and pushed them. Now i need some time for the other comments to grasp them and i will do the modification ASAP. I will come back to you if i have questions.

Thank you!

Copy link
Contributor

@tombuildsstuff tombuildsstuff left a comment

Choose a reason for hiding this comment

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

Hey @Kemyke

Thanks for pushing the latest updates - I've taken a look through and left a few comments but this is looking good :)

In addition to the comments we need to add the Resource Provider Registration to be able to provision Automation resources (for users who've not already registered them by hand / through the portal). We do this automatically when Terraform runs in this file, each Resource Provider is case sensitive, but it should just be a case of adding Microsoft.Automation to that list (which I've pulled from the Portal). Without the Resource Provider registered - users will see the error:

* azurerm_automation_account.example: automation.AccountClient#CreateOrUpdate: Failure responding to request: StatusCode=409 -- Original Error: autorest/azure: Service returned an error. Status=409 Code="MissingSubscriptionRegistration" Message="The subscription is not registered to use namespace 'Microsoft.Automation'. See https://aka.ms/rps-not-found for how to register subscriptions."

Otherwise, if we can fix the few comments I've noticed - we should be in a position to run the tests & merge this :)

Thanks!

account_name = "${azurerm_automation_account.example.name}"
username = "example_user"
password = "example_pwd"
description = "This is an example credential"
Copy link
Contributor

Choose a reason for hiding this comment

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

can we add in the missing } at the end of this example?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

if props := resp.CredentialProperties; props != nil {
d.Set("username", props.UserName)
}
d.Set("password", nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

if this isn't returned from the API - can we remove this line? this causes a perpetual diff in Terraform:

  ~ azurerm_automation_credential.example
      password: "<sensitive>" => "<sensitive>" (attribute changed)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

resource "azurerm_automation_runbook" "example" {
name = "Get-AzureVMTutorial"
location = "${azurerm_resource_group.example.location}"
resource_group_name = "${azurerm_resource_group.exmaple.name}"
Copy link
Contributor

Choose a reason for hiding this comment

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

can we update exmaple -> example

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

d.Set("description", props.Description)
}

d.Set("publish_content_link", nil) //publish content link is not set during Get()
Copy link
Contributor

Choose a reason for hiding this comment

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

from what I can see, this should be available in the response - is there a reason we're not setting this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was my first tought too but as you can see on the site you linked the publishContentLink is not listed in the example JSON. If i dump the result object of the Get it is also nil:

&automation.RunbookProperties{RunbookType:"PowerShellWorkflow", PublishContentLink:(*automation.ContentLink)(nil), State:"Published", LogVerbose:(*bool)(0xc420389168), LogProgress:(*
bool)(0xc420389169), LogActivityTrace:(*int32)(0xc42038916c), JobCount:(*int32)(0xc4203891fc), Parameters:(*map[string]*automation.RunbookParameter)(0xc4202b83c0), OutputTypes:(*[]string)(0xc4204c41e0), Draft:(*automation.RunbookDraft)(nil), ProvisioningState:"Succeeded"
, LastModifiedBy:(*string)(0xc420389260), CreationTime:(*date.Time)(0xc4204c4220), LastModifiedTime:(*date.Time)(0xc4204c4320), Description:(*string)(0xc4203891e0)}

In this case should i remove the nil association?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed, now its cleaner because it doesn't cause perpetual diff. As soon as it will be available in the response we can put it back.

"start_time": {
Type: schema.TypeString,
Required: true,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

could we add some validation to check this is at least 5 minutes in the future? we can do that with a ValidateFunc in the Schema. Submitting a date in the past returns the following error which isn't particularly helpful:

* azurerm_automation_schedule.example: automation.ScheduleClient#CreateOrUpdate: Failure responding to request: StatusCode=400 -- Original Error: autorest/azure: Service returned an error. Status=400 Code="Unknown" Message="Unknown service error"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

account_name = "${azurerm_automation_account.example.name}"
frequency = "OneTime"
timezone = "Central Europe Standard Time"
start_time = "2014-04-15T18:00:15-07:00"
Copy link
Contributor

Choose a reason for hiding this comment

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

can we update the timezone on this to be +02:00 - otherwise users seeing this will get a perpetual diff:

  ~ azurerm_automation_schedule.example
      start_time: "2017-10-16T03:00:00+02:00" => "2017-10-15T18:00:15-07:00"

Copy link
Contributor

Choose a reason for hiding this comment

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

also using this example it appears that seconds aren't a valid interval here - do we need to validate for that?

  ~ azurerm_automation_schedule.example
      start_time: "2017-10-15T18:00:00+02:00" => "2017-10-15T18:00:15+02:00"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think seconds are valid in the start time. You can set the start time on the Azure Website in second accuracy so i think 18:00:05 is a valid start time for a schedule.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. I only modified the timezone in the start_time.

@Kemyke
Copy link
Contributor Author

Kemyke commented Sep 6, 2017

Yeah, maybe you are right, azurerm_scheduled_time is maybe a good idea. In this case we can name the computed field as next_run_time.

What if we give an even more general name like azurerm_schedule or is it too general?

@Kemyke
Copy link
Contributor Author

Kemyke commented Sep 7, 2017

Hi @tombuildsstuff,

i have a next question regarding the azurerm_scheduled_time data source. If i am right i should use the GetOkExists function to retrieve for example the secondfield because the GetOk method returns ok = false if the value is 0. GetOkExists does what i actually need here. The problem is that this method was introduced in the beginning of august and it is not yet present in the azure provider source code. I think we should need to refresh the library under the vendor/github.com/hashicorp/terraform. Am i right? Is this something i can do? Is there any docs what are the steps to do this? Or can you explain how can i do it?

Regrdin this PR, could you take a look at the new code? Does it look good?

Thanks!

Copy link
Contributor

@tombuildsstuff tombuildsstuff left a comment

Choose a reason for hiding this comment

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

Hey @Kemyke

Thanks for the continued effort here - apologies for the delay in re-reviewing this. I've taken another look through and have left some more comments - but this is looking good 😄

Thanks!

Config: config,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMAutomationAccountExistsAndSku("azurerm_automation_account.test", automation.Basic),
),
Copy link
Contributor

Choose a reason for hiding this comment

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

can we update this to check the remote resource exists and then check the SKU in the state, rather than in Azure (since they should be the same, and the Import test should catch this not being set correctly in Azure):

resource.TestCheckResourceAttr(resourceName, "sku.0.name", "Basic"),

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

{
Config: config,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMAutomationAccountExistsAndSku("azurerm_automation_account.test", automation.Free),
Copy link
Contributor

Choose a reason for hiding this comment

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

(same here) - can we check the field in the state, rather than in Azure (as the Import tests do that for us)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

return fmt.Errorf("Bad: Get on automationClient: %s", err)
}

if resp.Sku.Name != sku {
Copy link
Contributor

Choose a reason for hiding this comment

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

(as above) could we remove this check and fall back to checking the state, for the reasons outlined above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

},
"description": {
Type: schema.TypeString,
Required: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

from what I can see, this isn't Required and would be better as Optional: true & Computed: true?

screen shot 2017-09-06 at 13 49 33

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a copy-paste bug i guess. Fixed!

return err
}

return fmt.Errorf("EventGrid Topic still exists:\n%#v", resp)
Copy link
Contributor

Choose a reason for hiding this comment

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

minor EventGrid Topic -> Automation Credential

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

cst := d.Get("start_time").(string)
starttime, tperr := time.Parse(time.RFC3339, cst)
if tperr != nil {
return fmt.Errorf("Cannot parse start_time: %s", cst)
Copy link
Contributor

Choose a reason for hiding this comment

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

can we update this formatting argument to be %q to quote it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

if v, ok := d.GetOk("timezone"); ok {
timezone = v.(string)
} else {
timezone = "UTC"
Copy link
Contributor

Choose a reason for hiding this comment

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

can we update the schema to set Default: "UTC"? That would allow us to just get this via d.Get()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

return nil
}

return fmt.Errorf("Error making Read request on AzureRM Automation Schedule '%s': %s", name, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

minor can we make the secondary formatting argument %+v to return the full error here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

d.Set("account_name", accName)
d.Set("frequency", resp.Frequency)
d.Set("description", resp.Description)
d.Set("start_time", string(resp.StartTime.Format(time.RFC3339)))
Copy link
Contributor

Choose a reason for hiding this comment

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

is there a reason we're not setting all the fields (such as timezone) here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, i just forgot to add that line. And the expiry time was also missing. I didn't even set the exipry time on the CreateOrUpdateProperties, i fixed that too.

{
Config: config,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMAutomationScheduleExistsAndFrequencyType("azurerm_automation_schedule.test", automation.OneTime),
Copy link
Contributor

Choose a reason for hiding this comment

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

(as above) - can we update this to be testCheckAzureRMAutomationScheduleExists and check the values in the state?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@Kemyke
Copy link
Contributor Author

Kemyke commented Sep 12, 2017

Hi @tombuildsstuff

can you please review the modifications? There is only one open quiestion, regarding the import_arm_automation_schedule_test.

Thanks!

@tombuildsstuff
Copy link
Contributor

Hey @Kemyke

Sorry for the delay getting back to you here - I've replied in-line about that issue, but a custom DiffSuppressFunc should solve that for us :)

Thanks!

@Kemyke
Copy link
Contributor Author

Kemyke commented Sep 18, 2017

Hi @tombuildsstuff

i implemented a DiffSuppressFunc. Please check it if it is correct, if the old or new value couldn't be parsed, it returns a false. Is it the correct behavior?
And you were right, i have to check the start_time value without the second part because the Azure trimms it. And i also attach this DiffSuppressFunc for the expiry_time field.

Please check the new code!

Thanks for the review!

Copy link
Contributor

@tombuildsstuff tombuildsstuff left a comment

Choose a reason for hiding this comment

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

Hey @Kemyke

Thanks for the continued effort on this PR - I've taken a look through and this LGTM :)

There's a couple of minor things which need doing to get this merged, but I'll take care of them now:

  • Vendoring the Azure SDK using govendor (rather than just coping the files in
  • Fixing the broken build (I caused this through a bad rebase, apologies)

I'll push updates for each of those shortly - but this otherwise LGTM :)

Thanks!

@tombuildsstuff
Copy link
Contributor

Tests pass:
screen shot 2017-09-20 at 09 52 22

@tombuildsstuff tombuildsstuff merged commit 97bcbc5 into hashicorp:master Sep 20, 2017
tombuildsstuff added a commit that referenced this pull request Sep 20, 2017
@Kemyke
Copy link
Contributor Author

Kemyke commented Sep 20, 2017

Hi @tombuildsstuff ,

wow that's a really good news. I will create the next PR, with the azurerm_scheduled_time data source soon.

In the meanwhile, as a contributor in the Azure Go SDK, there was an issue there (Azure/azure-sdk-for-go#725) which implies a bug report in the azure rest api spec project but that bug is a little bit stucked (Azure/azure-rest-api-specs#1545), don't you have influence to move that issue on a bit? Because it is immposible to create a peroidic schedule without that fix.

Thanks!

@ghost
Copy link

ghost commented Apr 1, 2020

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. If you feel I made an error 🤖 🙉 , please reach out to my human friends 👉 [email protected]. Thanks!

@ghost ghost locked and limited conversation to collaborators Apr 1, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants