转自:https://container-solutions.com/write-terraform-provider-part-1/

This is the first part of a series of blog posts that explain how to write Terraform providers.

Before we start I would like to state that this article asumes a couple of things from you:

  1. You have (some) experience with Terraform, the different provisioners and providers that come out of the box,
    its configuration files, tfstate files, etc.
  2. You are comfortable with the Go language and its code organization.

Because bootstrapping a Terraform provider can take some effort feel free to clone this Github repository to use it as your Terraform provider/plugin skeleton. It’ll also help you go along with all the steps that we will mention later on.


Let’s say that you want to write a Terraform provider for your awesome (cloud) provider. In practice, your Terraform configuration file would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
provider"awesome"{
  api_key     ="securetoken=="
  endpoint    ="https://api.example.org/v1"
  timeout     =60
  max_retries=5
}
 
resource"awesome_machine""speedy-server"{
  name="speedracer"
  cpus=4
  ram  =16384
}
 

So, your provider called awesome supports four different fields:

  • api_key
  • endpoint
  • timeout
  • max_retries

You also want to have your own resource called machine (notice here that because of the way Terraform works your resource name is prefixed with the name of your provider, hence awesome_machine and not just machine) which supports the following fields:

  • name
  • cpus
  • ram

Where to start?

Start by calling plugin.Serve, passing along a “provider” function that returns a terraform.ResourceProvider.

1
2
3
4
5
6
7
func main(){
  opts:=plugin.ServeOpts{
    ProviderFunc:Provider,// Read on to find the definition of this "Provider" function.
  }
  plugin.Serve(&opts)
}
 

Then define a function that returns an object that implements the terraform.ResourceProvider interface, specifically a schema.Provider:

1
2
3
4
5
6
7
8
func Provider()terraform.ResourceProvider{
  return&schema.Provider{
    Schema:        map[string]*schema.Schema{...},
    ResourcesMap:  map[string]*schema.Resource,
    ConfigureFunc:func(*schema.ResourceData)(interface{},error){...},
  }
}
 

This schema.Provider struct has three fields:

  • Schema: List of all the fields for your provider to work. Things like access tokens, log levels, endpoints, region, etc.
    The value of this field is a map[string]*schema.Schema, or in Spanish: a linked list where the key is a string and the value is a pointer to a schema.Schema.
    A minimalistic example schema would look like this:

    1
    2
    3
    4
    5
    6
    7
    8
    map[string]*schema.Schema{
      "api_key":&schema.Schema{
        Type:        schema.TypeString,
        Required:    true,
        Description:"Some short description here."
      }
    }
     

    Here we are saying that api_key is our configuration field in our configuration file; we are also specifying its type (schema.TypeString and not just string as this is required for Terraform to perform some validations when parsing the configuration file); we are also saying that is a required field: if the user does not specify a value for this field in the configuration file Terraform will throw an error and stop execution. Finally we add a short description to the field. There are more configuration options that can be specified for a schema field. You can see the complete list of fields of this struct here.

  • ResourcesMap: List of resources that you want to support in your Terraform configuration file. For example, if we were writing a Terraform provider for AWS and we wanted to support S3 buckets, Elastic Balancers and EC2 instances this is the place where you want to declare those resources.
    The value for this field is a map[string]*schema.Resource, similar to the one of the Schema field, the difference being that this list points to schema.Resource. Let’s take a look at one of the resources from the skeleton:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    map[string]*schema.Resource{
      "awesome_machine":&schema.Resource{
        Schema:map[string]*schema.Schema{
          "name":&schema.Schema{
            Type:     schema.TypeString,
            Required:true,
          },
        },
        SchemaVersion:1,
        Create:        func(d *schema.ResourceData,metainterface{}){},
        Read:          func(d *schema.ResourceData,metainterface{}){},
        Update:        func(d *schema.ResourceData,metainterface{}){},
        Delete:        func(d *schema.ResourceData,metainterface{}){},
      },
    }
     

    What we are doing here is until now pretty straight forward: we are declaring a list of resources. Each resource declaration has its own structure which is made out of a schema.Schema (we saw this already in the previous example when configuring the schema.Provider) and you probably also noticed that there are also a couple more fields like the SchemaVersion but I want to draw your attention specially towards the CreateReadUpdate & Delete ones. These are the four operations that Terraform will perform over the resources of your infrastructure and they will be called according to the case for each resource. This means that if you are creating four resources the Create function will be called four times. The same applies for the rest of the cases.
    The signature for these functions is func(*ResourceData, interface{}).
    The ResourceData type will provide you with some goodies for getting the values from the configuration file:

    • Get(key string): fetches the value for the given key. If the given key is not defined in the structure it will return nil. If the key has not been set in the configuration file then it will return the key’s type’s default value (0 for integers, “” for strings and so on).
    • GetChange(key string): Returns the old and new value for the given key.
    • HasChange(key string): Returns whether or not the given key has been changed.
    • SetId(): Sets the id for the given resource. If set to blank then the resource will be marked for deletion.

    It also offers a couple more methods (GetOkSetConnInfoSetPartial) that we won’t cover on this post.

    The second argument passed to the CRUD functions will be the value returned by your ConfigureFunc of your schema.Provider.
    Following our example, meta in this case can be safely casted to our ExampleClient like this:

    1
    2
    client:=meta.(*ExampleClient)
     

    Let’s now take a look at the createFunc source:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    func createFunc(d *schema.ResourceData,metainterface{})error{
      client:=meta.(*ExampleClient)
      machine:=Machine{
        Name:d.Get("name").(string),
        CPUs:d.Get("cpus").(int),
        RAM:  d.Get("ram").(int),
      }
     
      err:=client.CreateMachine(&machine)
      iferr!=nil{
        returnerr
      }
     
      d.SetId(machine.Id())
     
      returnnil
    }
     

    As mentioned before we know that meta is indeed a pointer to our ExampleClientso we cast it. The client offers a CreateMachine method which receives a pointer to a Machine object, so we initialize that object populating its fields with the values that the user put in the configuration file using the Get method of the ResourceData that has been passed to our function. Then we perform the client.CreateMachine call, passing along the machine that we declared before. After that we check for errors and make an early return in case that something went wrong with the creation of the machine. Finally, if everything went fine we will make a call to SetId. This not only sets the resource ID in the tfstate file but also tells Terraform that the resource was successfully created.
    For updating resources leverage the HasChange and GetChange functions. I will leave the implementation to your imagination and awesome software development capabilities.
    It is important also to mention that if at any point you set your resource id to blank Terraform will understand that the resource no longer exists. This is convenient, for example, when you want to synchronize your remote state with your local state (when a resource has been removed remotely). This is a common task for the readFunc function.

  • ConfigureFunc: Make use of this function when you need to initialize some client with the credentials defined in the Schema part. You can find its signature here.

Any other example?

Check the skeleton project. I recommend you use it for when you’re starting fresh with a new Terraform provider. Another good place to look for examples of complex use cases is the builtin providers that come along with Terraform.

Unit tests

When it comes to unit testing I suggest that you leave your Terraform provider as lightweight as possible. In the cases that we have worked on here at Container Solutions we have all the business logic in the client libraries (check for instance this Cobbler clientlibrary that we wrote) and has so far worked charms for us. Perhaps your use case is different. Perhaps not. Drop me a line either in the comments sections or on Twitter (@mongrelion). I would love to hear from you regarding this specific matter.

Final notes

This is not a full-grown Terraform provider. Far from it. But it will help you get started. Most of the documentation is in Terraform’s source code which can be tricky at first to browse around. This is a small effort to gather some of the basic concepts to reduce the barrier and help other developers get started as quick as possible. And again, as stated in the beginning of this article, this is only the first part of a series of upcoming blog posts that will talk more about Terraform providers.

Some of the things that we want to talk about in the future are:

  • Partial state (or how to recover from faulty resource modification)
  • More complex schema definitions
  • How to run callbacks once all your resources have been created/updated/deleted

And possibly much more. Leave a comment if there is anything else that you would like us to cover on these series and thanks for reading!

 
 
 
 

Write your own Terraform provider: Part 1的更多相关文章

  1. 京东云携手HashiCorp,宣布推出Terraform Provider

    2019年4月23日消息,京东云携手云基础设施自动化软件的领导者HashiCorp,宣布推出Terraform Provider for JD Cloud,这意味着用户能够在京东云上轻松使用简单模板语 ...

  2. terraform plugin 版本以及changlog 规范

    文章来自官方文章,转自:https://www.terraform.io/docs/extend/best-practices/versioning.html 里面包含了版本命名的规范,以及chang ...

  3. Developer Friendly | 基础设施即代码的事实标准Terraform已支持京东云!

    Developer Friendly | 基础设施即代码的事实标准Terraform已支持京东云! Chef.Puppet.Ansible.SaltStack 都可以称为配置管理工具,这些工具的主要目 ...

  4. 网易云terraform实践

    此文已由作者王慎为授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 一.terraform介绍 随着应用上云的常态化,资源栈动态管理的需求对用户也变得更加急切.资源编排(Res ...

  5. 零基础教程!一文教你使用Rancher 2.3和Terraform运行Windows容器

    本文来自Rancher Labs 介 绍 在Kubernetes 1.14版本中已经GA了对Windows的支持.这一结果凝结了一群优秀的工程师的努力,他们来自微软.Pivotal.VMware.红帽 ...

  6. 干货 | 运维福音——Terraform自动化管理京东云

    干货 | 运维福音--Terraform自动化管理京东云 原创: 张宏伟 京东云开发者社区  昨天 Terraform是一个高度可扩展的IT基础架构自动化编排工具,主张基础设施即代码,可通过代码集中管 ...

  7. Writing Custom Providers

    转自:https://www.terraform.io/docs/extend/writing-custom-providers.html 很详细,做为一个记录 In Terraform, a Pro ...

  8. 云原生之旅 - 6)不能错过的一款 Kubernetes 应用编排管理神器 Kustomize

    前言 相信经过前一篇文章的学习,大家已经对Helm有所了解,本篇文章介绍另一款工具 Kustomize,为什么Helm如此流行,还会出现 Kustomize?而且 Kustomize 自 kubect ...

  9. Terraform 自定义provider 开发

    内容来自官方文档,主要是进行学习自定义provider 开发的流程 开发说明 我们需要开发的有provider 以及resource 对于resource 我们需要进行crud 的处理,同时还需要进行 ...

随机推荐

  1. 运行java程序的方法-DOS命令和Eclipse方法

    ● 运行java程序的方法(使用DOS命令) 首先进行一个"文件夹选项"的设置: 在D:\Android\java_code目录下新建了一个Hello_World.java文件(不 ...

  2. Java NIO:浅析I/O模型(转)

    原文链接:http://www.cnblogs.com/dolphin0520/p/3916526.html 以下是本文的目录大纲: 一.什么是同步?什么是异步? 二.什么是阻塞?什么是非阻塞? 三. ...

  3. oracle概要文件profile详解

    一.目的: Oracle系统中的profile可以用来对用户所能使用的数据库资源进行限制,使用Create Profile命令创建一个Profile,用它来实现对数据库资源的限制使用,如果把该prof ...

  4. day 67 django orm的基础

    django项目 安装: 创建项目 配置(setting,static,csrf) 创建app,python manage.py startapp app1 三部分 urls.py路由配置 1,普通正 ...

  5. POJ 1321 棋盘问题 (dfs)

    在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别.要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C. ...

  6. STP实验(指定特定交换机为根桥)

    实验要求:将三层交换机设置为根桥交换机,并查看 拓扑如下: 涉及内容有: 1.根桥交换机的选举方式 2.生成树修改优先级成为根桥交换机 3.生成树直接指定根桥交换机 根桥交换机是根据优先级和MAC地址 ...

  7. 终极C语言指针

    // ex1.cpp : Defines the entry point for the console application. // #include "stdafx.h" # ...

  8. Golang游戏服务器与skynet的个人直观比较

    我对和GOLANG写MMO服务器的一些遐想: 1.沙盒(隔离性) SKYNET:原生LUA STATE作为沙盒, 进行服务器间隔离安全性高: 服务可以很容易的配置到不同节点之上. GO:估计用RECO ...

  9. C#类中字段封装为属性

    本文描述内容转载 https://zhidao.baidu.com/question/1174413218458798139.html 感谢 冥冥有你PD 的解答!!! 问题思索1 类成员包括变量和方 ...

  10. Unity调用Window提示框Yes/No(英文提示窗)

    Unity调用Windows弹提示框 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- ...