diff --git a/.go-version b/.go-version new file mode 100644 index 0000000000000..4a02d2c3170bd --- /dev/null +++ b/.go-version @@ -0,0 +1 @@ +1.16.2 diff --git a/upup/pkg/fi/cloudup/openstacktasks/BUILD.bazel b/upup/pkg/fi/cloudup/openstacktasks/BUILD.bazel index 7e24f97908afa..fe2821d90136e 100644 --- a/upup/pkg/fi/cloudup/openstacktasks/BUILD.bazel +++ b/upup/pkg/fi/cloudup/openstacktasks/BUILD.bazel @@ -41,6 +41,7 @@ go_library( "//pkg/pki:go_default_library", "//upup/pkg/fi:go_default_library", "//upup/pkg/fi/cloudup/openstack:go_default_library", + "//upup/pkg/fi/cloudup/terraform:go_default_library", "//util/pkg/vfs:go_default_library", "//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes:go_default_library", diff --git a/upup/pkg/fi/cloudup/openstacktasks/network.go b/upup/pkg/fi/cloudup/openstacktasks/network.go index 62f2d5b474ad7..818de59b3a03c 100644 --- a/upup/pkg/fi/cloudup/openstacktasks/network.go +++ b/upup/pkg/fi/cloudup/openstacktasks/network.go @@ -23,6 +23,7 @@ import ( "k8s.io/klog/v2" "k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi/cloudup/openstack" + "k8s.io/kops/upup/pkg/fi/cloudup/terraform" ) // +kops:fitask @@ -147,3 +148,21 @@ func (_ *Network) RenderOpenstack(t *openstack.OpenstackAPITarget, a, e, changes klog.V(2).Infof("Using an existing Openstack network, id=%s", fi.StringValue(e.ID)) return nil } + +type terraformNetwork struct { + Name *string `json:"name,omitempty" cty:"name"` +} + +func (_ *Network) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *Network) error { + tf := &terraformNetwork{ + // ID: e.TerraformLink(), + Name: e.Name, + } + + return t.RenderResource("openstack_networking_network_v2", *e.Name, tf) +} + +// TerraformLink fills in the property +func (e *Network) TerraformLink() *terraform.Literal { + return terraform.LiteralProperty("openstack_networking_network_v2", fi.StringValue(e.Name), "id") +} diff --git a/upup/pkg/fi/cloudup/openstacktasks/network_test.go b/upup/pkg/fi/cloudup/openstacktasks/network_test.go new file mode 100644 index 0000000000000..225b37960ab01 --- /dev/null +++ b/upup/pkg/fi/cloudup/openstacktasks/network_test.go @@ -0,0 +1,53 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package openstacktasks + +import ( + "testing" + + "k8s.io/kops/upup/pkg/fi" +) + +func TestNetworkTerraformRender(t *testing.T) { + cases := []*renderTest{ + { + Resource: &Network{ + Name: fi.String("test"), + }, + Expected: `provider "openstack" { + region = "nova" +} + +resource "openstack_networking_network_v2" "test" { + name = "test" +} + +terraform { + required_version = ">= 0.12.26" + required_providers { + openstack = { + "source" = "terraform-provider-openstack/openstack" + "version" = ">= 1.40.0" + } + } +} +`, + }, + } + + doRenderTests(t, "RenderTerraform", cases) +} diff --git a/upup/pkg/fi/cloudup/openstacktasks/render_test.go b/upup/pkg/fi/cloudup/openstacktasks/render_test.go new file mode 100644 index 0000000000000..744d9dcf4f62c --- /dev/null +++ b/upup/pkg/fi/cloudup/openstacktasks/render_test.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package openstacktasks + +import ( + "io/ioutil" + "os" + "path" + "reflect" + "testing" + + + "k8s.io/kops/pkg/diff" + "k8s.io/kops/upup/pkg/fi" + "k8s.io/kops/upup/pkg/fi/cloudup/openstack" + "k8s.io/kops/upup/pkg/fi/cloudup/terraform" +) + +type renderTest struct { + Resource interface{} + Expected string +} + +func doRenderTests(t *testing.T, method string, cases []*renderTest) { + outdir, err := ioutil.TempDir("", "kops-render-") + if err != nil { + t.Errorf("failed to create local render directory: %s", err) + t.FailNow() + } + defer func () { + err := os.RemoveAll(outdir) + if err != nil { + t.Errorf("failed to remove temp dir %q: %v", outdir, err) + } + }() + + for i, c := range cases { + var filename string + var target interface{} + + cloud := openstack.BuildMockOpenstackCloud("nova") + + switch method { + case "RenderTerraform": + target = terraform.NewTerraformTarget(cloud, "test", outdir, nil) + filename = "kubernetes.tf" + default: + t.Errorf("unknown render method: %s", method) + t.FailNow() + } + + // @step: build the inputs for the methods - hopefully these don't change between them + var inputs []reflect.Value + for _, x := range []interface{}{target, c.Resource, c.Resource, c.Resource} { + inputs = append(inputs, reflect.ValueOf(x)) + } + + err := func() error { + // @step: invoke the rendering method of the target + resp := reflect.ValueOf(c.Resource).MethodByName(method).Call(inputs) + if err := resp[0].Interface(); err != nil { + return err.(error) + } + + // @step: invoke the target finish up + in := []reflect.Value{reflect.ValueOf(make(map[string]fi.Task))} + resp = reflect.ValueOf(target).MethodByName("Finish").Call(in) + if err := resp[0].Interface(); err != nil { + return err.(error) + } + + // @step: check the render is as expected + if c.Expected != "" { + content, err := ioutil.ReadFile(path.Join(outdir, filename)) + if err != nil { + return err + } + if c.Expected != string(content) { + diffString := diff.FormatDiff(c.Expected, string(content)) + t.Logf("diff:\n%s\n", diffString) + t.Errorf("case %d, expected: %s\n,got: %s\n", i, c.Expected, string(content)) + //assert.Equal(t, "", string(content)) + } + } + + return nil + }() + if err != nil { + t.Errorf("case %d, did not expect an error: %s", i, err) + } + } +} \ No newline at end of file diff --git a/upup/pkg/fi/cloudup/terraform/target_hcl2.go b/upup/pkg/fi/cloudup/terraform/target_hcl2.go index 4ab72f7640738..0cdf3875dc656 100644 --- a/upup/pkg/fi/cloudup/terraform/target_hcl2.go +++ b/upup/pkg/fi/cloudup/terraform/target_hcl2.go @@ -115,6 +115,11 @@ func (t *TerraformTarget) finishHCL2(taskMap map[string]fi.Task) error { "version": cty.StringVal(">= 1.33.0"), }) } + } else if t.Cloud.ProviderID() == kops.CloudProviderOpenstack { + writeMap(requiredProvidersBody, "openstack", map[string]cty.Value{ + "source": cty.StringVal("terraform-provider-openstack/openstack"), + "version": cty.StringVal(">= 1.40.0"), + }) } bytes := hclwrite.Format(f.Bytes()) diff --git a/upup/pkg/fi/cloudup/terraform/target_json.go b/upup/pkg/fi/cloudup/terraform/target_json.go index 5368c8c47431a..f60867c7573bd 100644 --- a/upup/pkg/fi/cloudup/terraform/target_json.go +++ b/upup/pkg/fi/cloudup/terraform/target_json.go @@ -97,6 +97,14 @@ func (t *TerraformTarget) finishJSON(taskMap map[string]fi.Task) error { requiredProviderAWS[k] = v } requiredProvidersByName["aws"] = requiredProviderAWS + } else if t.Cloud.ProviderID() == kops.CloudProviderOpenstack { + requiredProviderOpenStack := make(map[string]interface{}) + requiredProviderOpenStack["source"] = "terraform-provider-openstack/openstack" + requiredProviderOpenStack["version"] = ">= 1.40.0" + for k, v := range tfGetProviderExtraConfig(t.clusterSpecTarget) { + requiredProviderOpenStack[k] = v + } + requiredProvidersByName["openstack"] = requiredProviderOpenStack } if len(requiredProvidersByName) != 0 {