anywhere you see <URL> change to your instance url (or localhost if not remote)
anywhere you see <NAME> change to a username (no spaces)
On the playground day go to https://lab.devopsplayground.org/
open
- Terminal
<url>/wetty
run cd ~/workdir/homeauto
- IDE if dont want to use vim to edit files
<url>:8000
If using the Playgrounds infrastruture skip to step 3.
- Open Terminal
docker-compose up --detach(--detach means we can still use the same terminal)- Go to
URL:8123 - Make account - not https so don't use an important password (location and name doesn't matter)
- Click finish
- Click on name (bottom left)
- Scroll to bottom of the page
- Create a token under Long-Lived Access Tokens
- Give it a name and click ok
- Save the token as we will use it later
- Click ok
- Rename terraform.tfvars.example to terraform.tfvars
- In the file replace YOUR-TOKEN in bearer_token= "YOUR-TOKEN" with the token you just made (If you have lost your token do step 2 again)
If not running on linux inside the build.sh linux_amd64 need to change to your system
- run
sh build.sh <NAME> 0.0.1remember to change<NAME>to your panda name- This with compile the provider and save it to go build -o ~/.terraform.d/plugins/github.com//homeauto/0.0.1/linux_amd64/terraform-provider-homeauto_v0.0.1
- The script will then run terraform plan and terraform apply
- We get errors about use not doing anything with the
bearer_tokenvariable but it works other than that
- Go in to
main.tfand set with provider up remember to change<NAME>
terraform {
required_providers {
homeauto={
version ="0.0.1"
source ="github.com/<NAME>/homeauto"
}
}
}
provider"homeauto" {
host="http://127.0.0.1:8123"bearer_token=var.bearer_token
} - In
variables.tfpaste
variablebearer_token{
sensitive=true
}- run
sh build.sh <NAME> 0.0.1remember to change<NAME>to your panda name- If you want to clean up the formatting in any of the terraform files just run
terraform fmt - This should have run though and not given any errors, Though it isn't doing anything at the moment
- If you want to clean up the formatting in any of the terraform files just run
- Open up
./provider/homeauto/provider.go - We have a skeleton of the Code to save time
- At the top of the file add
"net/http"under"context" - Replace the code for the
Provider()function with the following
funcProvider() *schema.Provider {
return&schema.Provider{
Schema: map[string]*schema.Schema{
"host": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("HOMEAUTO_HOST", nil),
Description: "There URL of the server: eg. `http://127.0.0.1:8123`",
},
"bearer_token": &schema.Schema{
Type: schema.TypeString,
Required: true,
Sensitive: true,
DefaultFunc: schema.EnvDefaultFunc("HOMEAUTO_BEARER_TOKEN", nil),
Description: "There bearer Token of the server",
},
},
ResourcesMap: map[string]*schema.Resource{
"homeauto_light": resourceLight(),
},
ConfigureContextFunc: providerConfigure,
}
}schemas are how we layout the data between go and terraform:
Schema: map[string]*schema.Schema{Is used to set the providers inputsResourcesMap: map[string]*schema.Resource{Is used to define what resources can be calledConfigureContextFunc: providerConfiguresays what function should be used to configure the provider
Replace the
providerConfigure()function with
funcproviderConfigure(ctx context.Context, d*schema.ResourceData) (interface{}, diag.Diagnostics) {
vardiags diag.DiagnosticsbearerToken:=d.Get("bearer_token").(string)
varhoststringhVal, ok:=d.GetOk("host")
ifok {
tempHost:=hVal.(string)
host=tempHost
}
c:=NewClient(host, bearerToken, &http.Client{})
returnc, diags
}- This is now the Provider set up, but it wont do anything until we create a resource
- Return to the
main.tffile - At the bottom of the file lets make a basic light. Paste the following
resource"homeauto_light""main" {
entity_id="light.virtual_light_10"state="on"
}- run
sh build.sh <NAME> 0.0.1remember to change<NAME>to your panda name- You should get a lovely error message saying not set up, but if you scroll up you can see the plan trying to make the lights.
- It knows what a light is as the schema is already made in
./provider/homeauto/resource_Light.go
- Head to
./provider/homeauto/resource_Light.go- You can see the schema has already been made but some things to point out
CreateContext: resourceLightCreate,
ReadContext: resourceLightRead,
UpdateContext: resourceLightUpdate,
DeleteContext: resourceLightDelete,- This sets the methods to be run by terraform on different steps
- Replace the
resourceLightCreate()function with:
funcresourceLightCreate(ctx context.Context, d*schema.ResourceData, minterface{}) diag.Diagnostics {
c:=m.(*Client)
item:=LightItem{
EntityID: d.Get("entity_id").(string),
State: d.Get("state").(string),
}
err:=StartLight(item,*c)
iferr!=nil {
returndiag.FromErr(err)
}
d.SetId(item.EntityID)
returnresourceLightRead(ctx, d, m)
}- run
sh build.sh <NAME> 0.0.1remember to change<NAME>to your panda name- Will through an error, but the light will be created
- If you now go back to
URL:8123, you should see a light appear
- Having a way of creating light but not saving anything about them isn't that useful
- still in
./provider/homeauto/resource_Light.golets add to theresourceLightReadfunction that will let terraform ask the api what states its in - replace the
resourceLightRead()function with
funcresourceLightRead(ctx context.Context, d*schema.ResourceData, minterface{}) diag.Diagnostics {
c:=m.(*Client)
vardiags diag.DiagnosticslightID:=d.Id()
light, err:=GetLight(lightID, *c)
iferr!=nil {
returndiag.FromErr(err)
}
iferr:=d.Set("state", light.State); err!=nil {
returndiag.FromErr(err)
}
returndiags
}- Have a look in to the
terraform.tfstatethat was made when we did theterraform applyin the last step, thestatus = tantedmeaning terraform doesn't trust the state - Delete the
terraform.tfstateandterraform.tfstate.backupfiles if they exsist - run
sh build.sh <NAME> 0.0.1remember to change<NAME>to your panda name -and look at theterraform.tfstatefile again, you will see the status value isnt there this time because the state can be tracked now
- One of the key features of terraform is being able to change the infrastructure by changing the .tf file. Lets implement that now.
- Still in the
./provider/homeauto/resource_Light.gofile replace theresourceLightUpdatefunction with:
funcresourceLightUpdate(ctx context.Context, d*schema.ResourceData, minterface{}) diag.Diagnostics {
c:=m.(*Client)
item:=LightItem{
EntityID: d.Get("entity_id").(string),
State: d.Get("state").(string),
}
err:=StartLight(item, *c)
iferr!=nil {
returndiag.FromErr(err)
}
returnresourceLightRead(ctx, d, m)
}- If you go back to the
main.tfand change the state to "off" and runsh build.sh NAME 0.0.1in the terminal if should turn the light "off"
- The last part we need to implement is
terraform destroy - To do this the following code should be put in to
resourceLightDelete:
funcresourceLightDelete(ctx context.Context, d*schema.ResourceData, minterface{}) diag.Diagnostics {
c:=m.(*Client)
vardiags diag.DiagnosticslightID:=d.Id()
err:=DeleteLight(lightID, *c)
iferr!=nil {
returndiag.FromErr(err)
}
d.SetId("")
returndiags
}- You might have to delete
"fmt"at the top of the file as we are no longer using the go model - If you now run
sh build.sh <NAME> 0.0.1to build the latest code - Then run
terraform destroyto destroy the light - Check
URL:8123and you should see the light no longer there
- If you have been looking around the tfstate and schemas you might have see there is information about the light we haven't used. Below it the code to get lights that can do more than just be on and off, replace all the code in
./provider/homeauto/resource_Light.gowith it. Have a read, have a play and ask if you have any questions:
package homeauto
import (
"context"" github.com/hashicorp/terraform-plugin-sdk/v2/diag""g ithub.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// resourceLight contains the definition of the recourse that terraform createsfuncresourceLight() *schema.Resource {
return&schema.Resource{
CreateContext: resourceLightCreate,
ReadContext: resourceLightRead,
UpdateContext: resourceLightUpdate,
DeleteContext: resourceLightDelete,
Schema: map[string]*schema.Schema{
"last_updated": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"entity_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"state": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "on",
},
"friendly_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "Light",
},
"color_mode": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "hs",
},
"brightness": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 255,
},
"white_value": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"supported_features": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 147,
},
"hs_color": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeFloat,
},
},
"rgb_color": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeInt,
},
},
"xy_color": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeFloat,
},
},
},
}
}
//resourceLightCreate is run when terraform apply creates a new resourcefuncresourceLightCreate(ctx context.Context, d*schema.ResourceData, minterface{}) diag.Diagnostics {
c:=m.(*Client)
item:=LightItem{
EntityID: d.Get("entity_id").(string),
State: d.Get("state").(string),
Attr: Attributes{
Brightness: d.Get("brightness").(int),
WhiteValue: d.Get("white_value").(int),
Name: d.Get("friendly_name").(string),
ColorMode: d.Get("color_mode").(string),
SupportedFeatures: d.Get("supported_features").(int),
},
}
ifd.Get("hs_color.#").(int) !=0 {
item.Attr.HsColor= []float64{d.Get("hs_color.0").(float64), d.Get("hs_color.1").(float64)}
}
ifd.Get("rgb_color.#").(int) !=0 {
item.Attr.RgbColor= []int{d.Get("rgb_color.0").(int), d.Get("rgb_color.1").(int), d.Get("rgb_color.2").(int)}
}
ifd.Get("xy_color.#").(int) !=0 {
item.Attr.XyColor= []float64{d.Get("xy_color.0").(float64), d.Get("xy_color.1").(float64)}
}
err:=StartLight(item, *c)
iferr!=nil {
returndiag.FromErr(err)
}
d.SetId(item.EntityID)
returnresourceLightRead(ctx, d, m)
}
//resourceLightRead is used to get the state of a light from the APIfuncresourceLightRead(ctx context.Context, d*schema.ResourceData, minterface{}) diag.Diagnostics {
c:=m.(*Client)
vardiags diag.DiagnosticslightID:=d.Id()
light, err:=GetLight(lightID, *c)
iferr!=nil {
returndiag.FromErr(err)
}
iferr:=d.Set("state", light.State); err!=nil {
returndiag.FromErr(err)
}
iferr:=d.Set("hs_color", light.Attr.HsColor); err!=nil {
returndiag.FromErr(err)
}
iferr:=d.Set("rgb_color", light.Attr.RgbColor); err!=nil {
returndiag.FromErr(err)
}
iferr:=d.Set("xy_color", light.Attr.XyColor); err!=nil {
returndiag.FromErr(err)
}
returndiags
}
//resourceLightUpdate is called when a light already exists but the state needs to changefuncresourceLightUpdate(ctx context.Context, d*schema.ResourceData, minterface{}) diag.Diagnostics {
c:=m.(*Client)
item:=LightItem{
EntityID: d.Get("entity_id").(string),
State: d.Get("state").(string),
Attr: Attributes{
Brightness: d.Get("brightness").(int),
WhiteValue: d.Get("white_value").(int),
Name: d.Get("friendly_name").(string),
ColorMode: d.Get("color_mode").(string),
SupportedFeatures: d.Get("supported_features").(int),
},
}
ifd.Get("hs_color.#").(int) !=0 {
item.Attr.HsColor= []float64{d.Get("hs_color.0").(float64), d.Get("hs_color.1").(float64)}
}
ifd.Get("rgb_color.#").(int) !=0 {
item.Attr.RgbColor= []int{d.Get("rgb_color.0").(int), d.Get("rgb_color.1").(int), d.Get("rgb_color.2").(int)}
}
ifd.Get("xy_color.#").(int) !=0 {
item.Attr.XyColor= []float64{d.Get("xy_color.0").(float64), d.Get("xy_color.1").(float64)}
}
err:=StartLight(item, *c)
iferr!=nil {
returndiag.FromErr(err)
}
returnresourceLightRead(ctx, d, m)
}
//resourceLightDelete is called when you run terraform delete or remove a resource from the statefuncresourceLightDelete(ctx context.Context, d*schema.ResourceData, minterface{}) diag.Diagnostics {
c:=m.(*Client)
vardiags diag.DiagnosticslightID:=d.Id()
err:=DeleteLight(lightID, *c)
iferr!=nil {
returndiag.FromErr(err)
}
d.SetId("")
returndiags
}-A light that makes use of the new code
resource"homeauto_light""colour" {
entity_id="light.virtual_light_12"state="on"brightness=100hs_color=[300.0, 71.0]
rgb_color=[255, 72, 255]
xy_color=[0.38, 0.17]
white_value=240friendly_name="Light 14"color_mode="hs"
}