diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 695d87c..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: build -on: - push: - branches: - - master - tags: - - '*' - pull_request: - branches: - - master - -jobs: - build: - name: Go ${{ matrix.go }} build - runs-on: ubuntu-18.04 - strategy: - matrix: - go: [ '1.14' ] - steps: - - uses: actions/checkout@v2 - - name: Setup go - uses: actions/setup-go@v1 - with: - go-version: ${{ matrix.go }} - - name: Test build - run: make - - release: - name: Release - if: startsWith(github.ref, 'refs/tags/') - runs-on: ubuntu-18.04 - needs: build - steps: - - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - # required for the changelog to work correctly - - name: Unshallow - run: git fetch --prune --unshallow - - - name: Set up Go - uses: actions/setup-go@v1 - with: - go-version: 1.14 - id: go - - - name: Run goreleaser - uses: goreleaser/goreleaser-action@v1 - with: - version: v0.131.1 - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..a196b97 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,17 @@ +name: Go Linter +on: + pull_request: + types: [opened, synchronize, reopened] +jobs: + golangci: + name: Run linter + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: v1.29 + only-new-issues: true \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..60a8ab5 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,19 @@ +name: Test suite +on: + pull_request: + types: [opened, synchronize, reopened] +jobs: + test: + name: Run tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: "1.15" + + - name: Test + run: make test build \ No newline at end of file diff --git a/resource_connection.go b/resource_connection.go index b2c4d18..7dfb95b 100644 --- a/resource_connection.go +++ b/resource_connection.go @@ -5,8 +5,61 @@ import ( "github.com/apache/airflow-client-go/airflow" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) +func resourceConnection() *schema.Resource { + return &schema.Resource{ + Create: resourceConnectionCreate, + Read: resourceConnectionRead, + Update: resourceConnectionUpdate, + Delete: resourceConnectionDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "connection_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "conn_type": { + Type: schema.TypeString, + Required: true, + }, + "host": { + Type: schema.TypeString, + Optional: true, + }, + "login": { + Type: schema.TypeString, + Optional: true, + }, + "schema": { + Type: schema.TypeString, + Optional: true, + }, + "port": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IsPortNumberOrZero, + }, + "password": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return old == "" + }, + }, + "extra": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + func resourceConnectionCreate(d *schema.ResourceData, m interface{}) error { pcfg := m.(ProviderConfig) client := pcfg.ApiClient @@ -17,6 +70,37 @@ func resourceConnectionCreate(d *schema.ResourceData, m interface{}) error { ConnectionId: &connId, ConnType: &connType, } + + if v, ok := d.GetOk("host"); ok { + val := v.(string) + conn.Host = *airflow.NewNullableString(&val) + } + + if v, ok := d.GetOk("login"); ok { + val := v.(string) + conn.Login = *airflow.NewNullableString(&val) + } + + if v, ok := d.GetOk("schema"); ok { + val := v.(string) + conn.Schema = *airflow.NewNullableString(&val) + } + + if v, ok := d.GetOk("port"); ok { + val := int32(v.(int)) + conn.Port = *airflow.NewNullableInt32(&val) + } + + if v, ok := d.GetOk("password"); ok { + val := v.(string) + conn.Password = &val + } + + if v, ok := d.GetOk("extra"); ok { + val := v.(string) + conn.Extra = *airflow.NewNullableString(&val) + } + connApi := client.ConnectionApi _, _, err := connApi.PostConnection(pcfg.AuthContext).Connection(conn).Execute() @@ -24,98 +108,95 @@ func resourceConnectionCreate(d *schema.ResourceData, m interface{}) error { return fmt.Errorf("failed to create connection `%s` from Airflow: %w", connId, err) } d.SetId(connId) + return resourceConnectionRead(d, m) } func resourceConnectionRead(d *schema.ResourceData, m interface{}) error { pcfg := m.(ProviderConfig) client := pcfg.ApiClient - connId := d.Get("connection_id").(string) - connection, resp, err := client.ConnectionApi.GetConnection(pcfg.AuthContext, connId).Execute() + connection, resp, err := client.ConnectionApi.GetConnection(pcfg.AuthContext, d.Id()).Execute() if resp != nil && resp.StatusCode == 404 { d.SetId("") return nil } if err != nil { - return fmt.Errorf("failed to get connection `%s` from Airflow: %w", connId, err) + return fmt.Errorf("failed to get connection `%s` from Airflow: %w", d.Id(), err) } d.Set("connection_id", connection.ConnectionId) d.Set("conn_type", connection.ConnType) + d.Set("host", connection.Host.Get()) + d.Set("login", connection.Login.Get()) + d.Set("schema", connection.Schema.Get()) + d.Set("port", connection.Port.Get()) + d.Set("password", connection.Password) + d.Set("extra", connection.Extra.Get()) + return nil } func resourceConnectionUpdate(d *schema.ResourceData, m interface{}) error { pcfg := m.(ProviderConfig) client := pcfg.ApiClient - connId := d.Get("connection_id").(string) + connId := d.Id() connType := d.Get("conn_type").(string) conn := airflow.Connection{ ConnectionId: &connId, ConnType: &connType, } + + if v, ok := d.GetOk("host"); ok { + val := v.(string) + conn.Host = *airflow.NewNullableString(&val) + } + + if v, ok := d.GetOk("login"); ok { + val := v.(string) + conn.Login = *airflow.NewNullableString(&val) + } + + if v, ok := d.GetOk("schema"); ok { + val := v.(string) + conn.Schema = *airflow.NewNullableString(&val) + } + + if v, ok := d.GetOk("port"); ok { + val := int32(v.(int)) + conn.Port = *airflow.NewNullableInt32(&val) + } + + if v, ok := d.GetOk("password"); ok { + val := v.(string) + conn.Password = &val + } + + if v, ok := d.GetOk("extra"); ok { + val := v.(string) + conn.Extra = *airflow.NewNullableString(&val) + } + _, _, err := client.ConnectionApi.PatchConnection(pcfg.AuthContext, connId).Connection(conn).Execute() if err != nil { return fmt.Errorf("failed to update connection `%s` from Airflow: %w", connId, err) } - d.SetId(connId) + return resourceConnectionRead(d, m) } func resourceConnectionDelete(d *schema.ResourceData, m interface{}) error { pcfg := m.(ProviderConfig) client := pcfg.ApiClient - connId := d.Get("connection_id").(string) - _, err := client.ConnectionApi.DeleteConnection(pcfg.AuthContext, connId).Execute() + + resp, err := client.ConnectionApi.DeleteConnection(pcfg.AuthContext, d.Id()).Execute() if err != nil { - return fmt.Errorf("failed to delete connection `%s` from Airflow: %w", connId, err) + return fmt.Errorf("failed to delete connection `%s` from Airflow: %w", d.Id(), err) } - return nil -} - -func resourceConnection() *schema.Resource { - return &schema.Resource{ - Create: resourceConnectionCreate, - Read: resourceConnectionRead, - Update: resourceConnectionUpdate, - Delete: resourceConnectionDelete, - - Schema: map[string]*schema.Schema{ - "connection_id": { - Type: schema.TypeString, - Required: true, - }, - "conn_type": { - Type: schema.TypeString, - Required: true, - }, - "host": { - Type: schema.TypeString, - Optional: true, - }, - "login": { - Type: schema.TypeString, - Optional: true, - }, - "schema": { - Type: schema.TypeString, - Optional: true, - }, - "port": { - Type: schema.TypeInt, - Optional: true, - }, - "password": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - }, - "extra": { - Type: schema.TypeString, - Optional: true, - }, - }, + if resp != nil && resp.StatusCode == 404 { + return nil } + + return nil } diff --git a/resource_connection_test.go b/resource_connection_test.go index 294be9a..1b12dc4 100644 --- a/resource_connection_test.go +++ b/resource_connection_test.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccAirflowConnection_basic(t *testing.T) { @@ -15,7 +16,7 @@ func TestAccAirflowConnection_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: nil, + CheckDestroy: testAccCheckAirflowConnectionCheckDestroy, Steps: []resource.TestStep{ { Config: testAccAirflowConnectionConfigBasic(rName), @@ -24,10 +25,83 @@ func TestAccAirflowConnection_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "conn_type", rName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAirflowConnection_full(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + rNameUpdated := acctest.RandomWithPrefix("tf-acc-test") + + resourceName := "airflow_connection.test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAirflowConnectionCheckDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAirflowConnectionConfigFull(rName, rName, 443), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "connection_id", rName), + resource.TestCheckResourceAttr(resourceName, "conn_type", rName), + resource.TestCheckResourceAttr(resourceName, "host", rName), + resource.TestCheckResourceAttr(resourceName, "login", rName), + resource.TestCheckResourceAttr(resourceName, "schema", rName), + resource.TestCheckResourceAttr(resourceName, "port", "443"), + // resource.TestCheckResourceAttr(resourceName, "password", rName), + resource.TestCheckResourceAttr(resourceName, "extra", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAirflowConnectionConfigFull(rName, rNameUpdated, 80), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "connection_id", rName), + resource.TestCheckResourceAttr(resourceName, "conn_type", rName), + resource.TestCheckResourceAttr(resourceName, "host", rNameUpdated), + resource.TestCheckResourceAttr(resourceName, "login", rNameUpdated), + resource.TestCheckResourceAttr(resourceName, "schema", rNameUpdated), + resource.TestCheckResourceAttr(resourceName, "port", "80"), + // resource.TestCheckResourceAttr(resourceName, "password", rName), + resource.TestCheckResourceAttr(resourceName, "extra", rNameUpdated), + ), + }, }, }) } +func testAccCheckAirflowConnectionCheckDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(ProviderConfig) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "airflow_connection" { + continue + } + + conn, res, err := client.ApiClient.ConnectionApi.GetConnection(client.AuthContext, rs.Primary.ID).Execute() + if err == nil { + if *conn.ConnectionId == rs.Primary.ID { + return fmt.Errorf("Airflow Connection (%s) still exists.", rs.Primary.ID) + } + } + + if res != nil && res.StatusCode == 404 { + continue + } + } + + return nil +} + func testAccAirflowConnectionConfigBasic(rName string) string { return fmt.Sprintf(` resource "airflow_connection" "test" { @@ -36,3 +110,18 @@ resource "airflow_connection" "test" { } `, rName) } + +func testAccAirflowConnectionConfigFull(rName, rName2 string, port int) string { + return fmt.Sprintf(` +resource "airflow_connection" "test" { + connection_id = %[1]q + conn_type = %[1]q + host = %[2]q + login = %[2]q + schema = %[2]q + port = %[3]d + password = %[2]q + extra = %[2]q +} +`, rName, rName2, port) +} diff --git a/resource_variable.go b/resource_variable.go index 237d748..26f87a1 100644 --- a/resource_variable.go +++ b/resource_variable.go @@ -7,6 +7,29 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +func resourceVariable() *schema.Resource { + return &schema.Resource{ + Create: resourceVariableCreate, + Read: resourceVariableRead, + Update: resourceVariableUpdate, + Delete: resourceVariableDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + func resourceVariableCreate(d *schema.ResourceData, m interface{}) error { pcfg := m.(ProviderConfig) client := pcfg.ApiClient @@ -22,32 +45,35 @@ func resourceVariableCreate(d *schema.ResourceData, m interface{}) error { return fmt.Errorf("failed to create variable `%s` from Airflow: %w", key, err) } d.SetId(key) + return resourceVariableRead(d, m) } func resourceVariableRead(d *schema.ResourceData, m interface{}) error { pcfg := m.(ProviderConfig) client := pcfg.ApiClient - key := d.Get("key").(string) - variable, resp, err := client.VariableApi.GetVariable(pcfg.AuthContext, key).Execute() + + variable, resp, err := client.VariableApi.GetVariable(pcfg.AuthContext, d.Id()).Execute() if resp != nil && resp.StatusCode == 404 { d.SetId("") return nil } if err != nil { - return fmt.Errorf("failed to get variable `%s` from Airflow: %w", key, err) + return fmt.Errorf("failed to get variable `%s` from Airflow: %w", d.Id(), err) } d.Set("key", variable.Key) d.Set("value", variable.Value) + return nil } func resourceVariableUpdate(d *schema.ResourceData, m interface{}) error { pcfg := m.(ProviderConfig) client := pcfg.ApiClient + val := d.Get("value").(string) - key := d.Get("key").(string) + key := d.Id() _, _, err := client.VariableApi.PatchVariable(pcfg.AuthContext, key).Variable(airflow.Variable{ Key: &key, Value: &val, @@ -55,38 +81,22 @@ func resourceVariableUpdate(d *schema.ResourceData, m interface{}) error { if err != nil { return fmt.Errorf("failed to update variable `%s` from Airflow: %w", key, err) } - d.SetId(key) + return resourceVariableRead(d, m) } func resourceVariableDelete(d *schema.ResourceData, m interface{}) error { pcfg := m.(ProviderConfig) client := pcfg.ApiClient - key := d.Get("key").(string) - _, err := client.VariableApi.DeleteVariable(pcfg.AuthContext, key).Execute() + + resp, err := client.VariableApi.DeleteVariable(pcfg.AuthContext, d.Id()).Execute() if err != nil { - return fmt.Errorf("failed to delete variable `%s` from Airflow: %w", key, err) + return fmt.Errorf("failed to delete variable `%s` from Airflow: %w", d.Id(), err) } - return nil -} - -func resourceVariable() *schema.Resource { - return &schema.Resource{ - Create: resourceVariableCreate, - Read: resourceVariableRead, - Update: resourceVariableUpdate, - Delete: resourceVariableDelete, - - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - }, - "value": { - Type: schema.TypeString, - Required: true, - }, - }, + if resp != nil && resp.StatusCode == 404 { + return nil } + + return nil } diff --git a/resource_variable_test.go b/resource_variable_test.go index 94af986..54c0494 100644 --- a/resource_variable_test.go +++ b/resource_variable_test.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccAirflowVariable_basic(t *testing.T) { @@ -16,7 +17,7 @@ func TestAccAirflowVariable_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: nil, + CheckDestroy: testAccCheckAirflowVariableCheckDestroy, Steps: []resource.TestStep{ { Config: testAccAirflowVariableConfigBasic(rName, rName), @@ -25,6 +26,11 @@ func TestAccAirflowVariable_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "value", rName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccAirflowVariableConfigBasic(rName, rNameUpdated), Check: resource.ComposeTestCheckFunc( @@ -36,6 +42,29 @@ func TestAccAirflowVariable_basic(t *testing.T) { }) } +func testAccCheckAirflowVariableCheckDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(ProviderConfig) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "airflow_variable" { + continue + } + + variable, res, err := client.ApiClient.VariableApi.GetVariable(client.AuthContext, rs.Primary.ID).Execute() + if err == nil { + if *variable.Key == rs.Primary.ID { + return fmt.Errorf("Airflow Variable (%s) still exists.", rs.Primary.ID) + } + } + + if res != nil && res.StatusCode == 404 { + continue + } + } + + return nil +} + func testAccAirflowVariableConfigBasic(rName, value string) string { return fmt.Sprintf(` resource "airflow_variable" "test" {