mirror of https://github.com/kubernetes/kops.git
Merge pull request #13060 from srikiz/DO-Add-New-VPC
[DigitalOcean] Implement new VPC if network-cidr flag is specified
This commit is contained in:
commit
e29591e21e
|
|
@ -73,6 +73,21 @@ kops create cluster --cloud=digitalocean --name=dev5.k8s.local --networking=cili
|
||||||
kops delete cluster dev5.k8s.local --yes
|
kops delete cluster dev5.k8s.local --yes
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## VPC Support
|
||||||
|
|
||||||
|
If you already have a VPC created and want to run kops cluster in this vpc, specify the vpc uuid as below.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/kops create cluster --cloud=digitalocean --name=dev1.example.com --vpc=af287488-862e-46c7-a783-5e5fa89cb200 --networking=cilium --zones=tor1 --ssh-public-key=~/.ssh/id_rsa.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to create a new VPC for running your kops cluster, specify the network-cidr as below.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./kops create cluster --cloud=digitalocean --name=dev1.example.com --networking=calico --network-cidr=192.168.11.0/24 --zones=nyc1 --ssh-public-key=~/.ssh/id_rsa.pub --yes
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Features Still in Development
|
## Features Still in Development
|
||||||
|
|
||||||
kOps for DigitalOcean currently does not support these features:
|
kOps for DigitalOcean currently does not support these features:
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,8 @@ It is recommended to keep using the `v1alpha2` API version.
|
||||||
|
|
||||||
* Fix inconsistent output of `kops get clusters -ojson`. This will now always return a list (irrespective of a single or multiple clusters) to keep the format consistent. However, note that `kops get cluster dev.example.com -ojson` will continue to work as previously, and will return a single object.
|
* Fix inconsistent output of `kops get clusters -ojson`. This will now always return a list (irrespective of a single or multiple clusters) to keep the format consistent. However, note that `kops get cluster dev.example.com -ojson` will continue to work as previously, and will return a single object.
|
||||||
|
|
||||||
|
* Digital Ocean kops now has vpc support. You can specify a `network-cidr` range while creating the kops cluster. kops resources will be created in the new vpc range. Also supports shared vpc; you can specify the vpc uuid while creating kops cluster.
|
||||||
|
|
||||||
# Full change list since 1.22.0 release
|
# Full change list since 1.22.0 release
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,10 @@ type ClusterSpec struct {
|
||||||
MasterPublicName string `json:"masterPublicName,omitempty"`
|
MasterPublicName string `json:"masterPublicName,omitempty"`
|
||||||
// MasterInternalName is the internal DNS name for the master nodes
|
// MasterInternalName is the internal DNS name for the master nodes
|
||||||
MasterInternalName string `json:"masterInternalName,omitempty"`
|
MasterInternalName string `json:"masterInternalName,omitempty"`
|
||||||
// NetworkCIDR is the CIDR used for the AWS VPC / GCE Network, or otherwise allocated to k8s
|
// NetworkCIDR is the CIDR used for the AWS VPC / DO/ GCE Network, or otherwise allocated to k8s
|
||||||
// This is a real CIDR, not the internal k8s network
|
// This is a real CIDR, not the internal k8s network
|
||||||
// On AWS, it maps to the VPC CIDR. It is not required on GCE.
|
// On AWS, it maps to the VPC CIDR. It is not required on GCE.
|
||||||
|
// On DO, it maps to the VPC CIDR.
|
||||||
NetworkCIDR string `json:"networkCIDR,omitempty"`
|
NetworkCIDR string `json:"networkCIDR,omitempty"`
|
||||||
// AdditionalNetworkCIDRs is a list of additional CIDR used for the AWS VPC
|
// AdditionalNetworkCIDRs is a list of additional CIDR used for the AWS VPC
|
||||||
// or otherwise allocated to k8s. This is a real CIDR, not the internal k8s network
|
// or otherwise allocated to k8s. This is a real CIDR, not the internal k8s network
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,7 @@ func ValidateCluster(c *kops.Cluster, strict bool) field.ErrorList {
|
||||||
requiresSubnets = false
|
requiresSubnets = false
|
||||||
requiresSubnetCIDR = false
|
requiresSubnetCIDR = false
|
||||||
requiresNetworkCIDR = false
|
requiresNetworkCIDR = false
|
||||||
if c.Spec.NetworkCIDR != "" {
|
|
||||||
allErrs = append(allErrs, field.Forbidden(fieldSpec.Child("networkCIDR"), "networkCIDR should not be set on DigitalOcean"))
|
|
||||||
}
|
|
||||||
case kops.CloudProviderAWS:
|
case kops.CloudProviderAWS:
|
||||||
case kops.CloudProviderAzure:
|
case kops.CloudProviderAzure:
|
||||||
case kops.CloudProviderOpenstack:
|
case kops.CloudProviderOpenstack:
|
||||||
|
|
@ -135,6 +133,16 @@ func ValidateCluster(c *kops.Cluster, strict bool) field.ErrorList {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
allErrs = append(allErrs, field.Invalid(fieldSpec.Child("networkCIDR"), c.Spec.NetworkCIDR, "Cluster had an invalid networkCIDR"))
|
allErrs = append(allErrs, field.Invalid(fieldSpec.Child("networkCIDR"), c.Spec.NetworkCIDR, "Cluster had an invalid networkCIDR"))
|
||||||
}
|
}
|
||||||
|
if kops.CloudProviderID(c.Spec.CloudProvider) == kops.CloudProviderDO {
|
||||||
|
// verify if the NetworkCIDR is in a private range as per RFC1918
|
||||||
|
if !networkCIDR.IP.IsPrivate() {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fieldSpec.Child("networkCIDR"), c.Spec.NetworkCIDR, "Cluster had a networkCIDR outside the private IP range"))
|
||||||
|
}
|
||||||
|
// verify if networkID is not specified. In case of DO, this is mutually exclusive.
|
||||||
|
if c.Spec.NetworkID != "" {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(fieldSpec.Child("networkCIDR"), "DO doesn't support specifying both NetworkID and NetworkCIDR together"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ go_library(
|
||||||
"api_loadbalancer.go",
|
"api_loadbalancer.go",
|
||||||
"context.go",
|
"context.go",
|
||||||
"droplets.go",
|
"droplets.go",
|
||||||
|
"network.go",
|
||||||
],
|
],
|
||||||
importpath = "k8s.io/kops/pkg/model/domodel",
|
importpath = "k8s.io/kops/pkg/model/domodel",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package domodel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/kops/pkg/apis/kops"
|
"k8s.io/kops/pkg/apis/kops"
|
||||||
"k8s.io/kops/pkg/dns"
|
"k8s.io/kops/pkg/dns"
|
||||||
|
|
@ -56,7 +55,7 @@ func (b *APILoadBalancerModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
||||||
return fmt.Errorf("unhandled LoadBalancer type %q", lbSpec.Type)
|
return fmt.Errorf("unhandled LoadBalancer type %q", lbSpec.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
clusterName := strings.Replace(b.ClusterName(), ".", "-", -1)
|
clusterName := do.SafeClusterName(b.ClusterName())
|
||||||
loadbalancerName := "api-" + clusterName
|
loadbalancerName := "api-" + clusterName
|
||||||
clusterMasterTag := do.TagKubernetesClusterMasterPrefix + ":" + clusterName
|
clusterMasterTag := do.TagKubernetesClusterMasterPrefix + ":" + clusterName
|
||||||
|
|
||||||
|
|
@ -67,6 +66,15 @@ func (b *APILoadBalancerModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
||||||
DropletTag: fi.String(clusterMasterTag),
|
DropletTag: fi.String(clusterMasterTag),
|
||||||
Lifecycle: b.Lifecycle,
|
Lifecycle: b.Lifecycle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.Cluster.Spec.NetworkID != "" {
|
||||||
|
loadbalancer.VPCUUID = fi.String(b.Cluster.Spec.NetworkID)
|
||||||
|
} else if b.Cluster.Spec.NetworkCIDR != "" {
|
||||||
|
vpcName := "vpc-" + clusterName
|
||||||
|
loadbalancer.VPCName = fi.String(vpcName)
|
||||||
|
loadbalancer.NetworkCIDR = fi.String(b.Cluster.Spec.NetworkCIDR)
|
||||||
|
}
|
||||||
|
|
||||||
c.AddTask(loadbalancer)
|
c.AddTask(loadbalancer)
|
||||||
|
|
||||||
// Temporarily do not know the role of the following function
|
// Temporarily do not know the role of the following function
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,9 @@ func (d *DropletBuilder) Build(c *fi.ModelBuilderContext) error {
|
||||||
sshKeyFingerPrint := splitSSHKeyName[len(splitSSHKeyName)-1]
|
sshKeyFingerPrint := splitSSHKeyName[len(splitSSHKeyName)-1]
|
||||||
|
|
||||||
// replace "." with "-" since DO API does not accept "."
|
// replace "." with "-" since DO API does not accept "."
|
||||||
clusterTag := do.TagKubernetesClusterNamePrefix + ":" + strings.Replace(d.ClusterName(), ".", "-", -1)
|
clusterName := do.SafeClusterName(d.ClusterName())
|
||||||
clusterMasterTag := do.TagKubernetesClusterMasterPrefix + ":" + strings.Replace(d.ClusterName(), ".", "-", -1)
|
clusterTag := do.TagKubernetesClusterNamePrefix + ":" + clusterName
|
||||||
|
clusterMasterTag := do.TagKubernetesClusterMasterPrefix + ":" + clusterName
|
||||||
|
|
||||||
masterIndexCount := 0
|
masterIndexCount := 0
|
||||||
// In the future, DigitalOcean will use Machine API to manage groups,
|
// In the future, DigitalOcean will use Machine API to manage groups,
|
||||||
|
|
@ -81,7 +82,13 @@ func (d *DropletBuilder) Build(c *fi.ModelBuilderContext) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.Cluster.Spec.NetworkID != "" {
|
if d.Cluster.Spec.NetworkID != "" {
|
||||||
droplet.VPC = fi.String(d.Cluster.Spec.NetworkID)
|
droplet.VPCUUID = fi.String(d.Cluster.Spec.NetworkID)
|
||||||
|
} else if d.Cluster.Spec.NetworkCIDR != "" {
|
||||||
|
// since networkCIDR specified as part of the request, it is made sure that vpc with this cidr exist before
|
||||||
|
// creating the droplet, so you can associate with vpc uuid for this droplet.
|
||||||
|
vpcName := "vpc-" + clusterName
|
||||||
|
droplet.VPCName = fi.String(vpcName)
|
||||||
|
droplet.NetworkCIDR = fi.String(d.Cluster.Spec.NetworkCIDR)
|
||||||
}
|
}
|
||||||
|
|
||||||
userData, err := d.BootstrapScriptBuilder.ResourceNodeUp(c, ig)
|
userData, err := d.BootstrapScriptBuilder.ResourceNodeUp(c, ig)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
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 domodel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/kops/upup/pkg/fi"
|
||||||
|
"k8s.io/kops/upup/pkg/fi/cloudup/dotasks"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NetworkModelBuilder configures network objects
|
||||||
|
type NetworkModelBuilder struct {
|
||||||
|
*DOModelContext
|
||||||
|
Lifecycle fi.Lifecycle
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fi.ModelBuilder = &NetworkModelBuilder{}
|
||||||
|
|
||||||
|
func (b *NetworkModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
||||||
|
|
||||||
|
ipRange := b.Cluster.Spec.NetworkCIDR
|
||||||
|
if ipRange == "" {
|
||||||
|
// no cidr specified, use the default vpc in DO that's always available
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterName := strings.Replace(b.ClusterName(), ".", "-", -1)
|
||||||
|
vpcName := "vpc-" + clusterName
|
||||||
|
|
||||||
|
// Create a separate vpc for this cluster.
|
||||||
|
vpc := &dotasks.VPC{
|
||||||
|
Name: fi.String(vpcName),
|
||||||
|
Region: fi.String(b.Cluster.Spec.Subnets[0].Region),
|
||||||
|
Lifecycle: b.Lifecycle,
|
||||||
|
IPRange: fi.String(ipRange),
|
||||||
|
}
|
||||||
|
c.AddTask(vpc)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -40,6 +40,7 @@ const (
|
||||||
resourceTypeVolume = "volume"
|
resourceTypeVolume = "volume"
|
||||||
resourceTypeDNSRecord = "dns-record"
|
resourceTypeDNSRecord = "dns-record"
|
||||||
resourceTypeLoadBalancer = "loadbalancer"
|
resourceTypeLoadBalancer = "loadbalancer"
|
||||||
|
resourceTypeVPC = "vpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type listFn func(fi.Cloud, string) ([]*resources.Resource, error)
|
type listFn func(fi.Cloud, string) ([]*resources.Resource, error)
|
||||||
|
|
@ -52,6 +53,7 @@ func ListResources(cloud do.DOCloud, clusterName string) (map[string]*resources.
|
||||||
listDroplets,
|
listDroplets,
|
||||||
listDNS,
|
listDNS,
|
||||||
listLoadBalancers,
|
listLoadBalancers,
|
||||||
|
listVPCs,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, fn := range listFunctions {
|
for _, fn := range listFunctions {
|
||||||
|
|
@ -261,6 +263,16 @@ func deleteDroplet(cloud fi.Cloud, t *resources.Resource) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteVPC(cloud fi.Cloud, t *resources.Resource) error {
|
||||||
|
c := cloud.(do.DOCloud)
|
||||||
|
_, err := c.VPCsService().Delete(context.TODO(), t.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete VPC %s (ID %s): %s", t.Name, t.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func deleteVolume(cloud fi.Cloud, t *resources.Resource) error {
|
func deleteVolume(cloud fi.Cloud, t *resources.Resource) error {
|
||||||
c := cloud.(do.DOCloud)
|
c := cloud.(do.DOCloud)
|
||||||
volume := t.Obj.(godo.Volume)
|
volume := t.Obj.(godo.Volume)
|
||||||
|
|
@ -370,3 +382,32 @@ func dumpDroplet(op *resources.DumpOperation, r *resources.Resource) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listVPCs(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) {
|
||||||
|
c := cloud.(do.DOCloud)
|
||||||
|
var resourceTrackers []*resources.Resource
|
||||||
|
|
||||||
|
clusterName = do.SafeClusterName(clusterName)
|
||||||
|
vpcName := "vpc-" + clusterName
|
||||||
|
|
||||||
|
vpcs, err := c.GetAllVPCs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list vpcs: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vpc := range vpcs {
|
||||||
|
if vpc.Name == vpcName {
|
||||||
|
resourceTracker := &resources.Resource{
|
||||||
|
Name: vpc.Name,
|
||||||
|
ID: vpc.ID,
|
||||||
|
Type: resourceTypeVPC,
|
||||||
|
Deleter: deleteVPC,
|
||||||
|
Obj: vpc,
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceTrackers = append(resourceTrackers, resourceTracker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceTrackers, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -585,6 +585,7 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error {
|
||||||
l.Builders = append(l.Builders,
|
l.Builders = append(l.Builders,
|
||||||
&domodel.APILoadBalancerModelBuilder{DOModelContext: doModelContext, Lifecycle: securityLifecycle},
|
&domodel.APILoadBalancerModelBuilder{DOModelContext: doModelContext, Lifecycle: securityLifecycle},
|
||||||
&domodel.DropletBuilder{DOModelContext: doModelContext, BootstrapScriptBuilder: bootstrapScriptBuilder, Lifecycle: clusterLifecycle},
|
&domodel.DropletBuilder{DOModelContext: doModelContext, BootstrapScriptBuilder: bootstrapScriptBuilder, Lifecycle: clusterLifecycle},
|
||||||
|
&domodel.NetworkModelBuilder{DOModelContext: doModelContext, Lifecycle: networkLifecycle},
|
||||||
)
|
)
|
||||||
case kops.CloudProviderGCE:
|
case kops.CloudProviderGCE:
|
||||||
gceModelContext := &gcemodel.GCEModelContext{
|
gceModelContext := &gcemodel.GCEModelContext{
|
||||||
|
|
|
||||||
|
|
@ -70,10 +70,13 @@ type DOCloud interface {
|
||||||
LoadBalancersService() godo.LoadBalancersService
|
LoadBalancersService() godo.LoadBalancersService
|
||||||
DomainService() godo.DomainsService
|
DomainService() godo.DomainsService
|
||||||
ActionsService() godo.ActionsService
|
ActionsService() godo.ActionsService
|
||||||
|
VPCsService() godo.VPCsService
|
||||||
FindClusterStatus(cluster *kops.Cluster) (*kops.ClusterStatus, error)
|
FindClusterStatus(cluster *kops.Cluster) (*kops.ClusterStatus, error)
|
||||||
GetAllLoadBalancers() ([]godo.LoadBalancer, error)
|
GetAllLoadBalancers() ([]godo.LoadBalancer, error)
|
||||||
GetAllDropletsByTag(tag string) ([]godo.Droplet, error)
|
GetAllDropletsByTag(tag string) ([]godo.Droplet, error)
|
||||||
GetAllVolumesByRegion() ([]godo.Volume, error)
|
GetAllVolumesByRegion() ([]godo.Volume, error)
|
||||||
|
GetVPCUUID(networkCIDR string, vpcName string) (string, error)
|
||||||
|
GetAllVPCs() ([]*godo.VPC, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var readBackoff = wait.Backoff{
|
var readBackoff = wait.Backoff{
|
||||||
|
|
@ -238,11 +241,44 @@ func (c *doCloudImplementation) ActionsService() godo.ActionsService {
|
||||||
return c.Client.Actions
|
return c.Client.Actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *doCloudImplementation) VPCsService() godo.VPCsService {
|
||||||
|
return c.Client.VPCs
|
||||||
|
}
|
||||||
|
|
||||||
// FindVPCInfo is not implemented, it's only here to satisfy the fi.Cloud interface
|
// FindVPCInfo is not implemented, it's only here to satisfy the fi.Cloud interface
|
||||||
func (c *doCloudImplementation) FindVPCInfo(id string) (*fi.VPCInfo, error) {
|
func (c *doCloudImplementation) FindVPCInfo(id string) (*fi.VPCInfo, error) {
|
||||||
return nil, errors.New("not implemented")
|
return nil, errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *doCloudImplementation) GetVPCUUID(networkCIDR string, vpcName string) (string, error) {
|
||||||
|
vpcUUID := ""
|
||||||
|
done, err := vfs.RetryWithBackoff(readBackoff, func() (bool, error) {
|
||||||
|
vpcs, err := c.GetAllVPCs()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vpc := range vpcs {
|
||||||
|
if vpc.IPRange == networkCIDR && vpc.Name == vpcName {
|
||||||
|
vpcUUID = vpc.ID
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("vpc not yet created..")
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if done {
|
||||||
|
return vpcUUID, nil
|
||||||
|
} else {
|
||||||
|
return "", wait.ErrWaitTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *doCloudImplementation) GetApiIngressStatus(cluster *kops.Cluster) ([]fi.ApiIngressStatus, error) {
|
func (c *doCloudImplementation) GetApiIngressStatus(cluster *kops.Cluster) ([]fi.ApiIngressStatus, error) {
|
||||||
var ingresses []fi.ApiIngressStatus
|
var ingresses []fi.ApiIngressStatus
|
||||||
done, err := vfs.RetryWithBackoff(readBackoff, func() (bool, error) {
|
done, err := vfs.RetryWithBackoff(readBackoff, func() (bool, error) {
|
||||||
|
|
@ -532,6 +568,33 @@ func (c *doCloudImplementation) GetAllLoadBalancers() ([]godo.LoadBalancer, erro
|
||||||
return allLoadBalancers, nil
|
return allLoadBalancers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *doCloudImplementation) GetAllVPCs() ([]*godo.VPC, error) {
|
||||||
|
allVPCs := []*godo.VPC{}
|
||||||
|
|
||||||
|
opt := &godo.ListOptions{}
|
||||||
|
for {
|
||||||
|
vpcs, resp, err := c.VPCsService().List(context.TODO(), opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allVPCs = append(allVPCs, vpcs...)
|
||||||
|
|
||||||
|
if resp.Links == nil || resp.Links.IsLastPage() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
page, err := resp.Links.CurrentPage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
opt.Page = page + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return allVPCs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *doCloudImplementation) GetAllDropletsByTag(tag string) ([]godo.Droplet, error) {
|
func (c *doCloudImplementation) GetAllDropletsByTag(tag string) ([]godo.Droplet, error) {
|
||||||
allDroplets := []godo.Droplet{}
|
allDroplets := []godo.Droplet{}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,3 +130,15 @@ func (c *doCloudMockImplementation) GetAllDropletsByTag(tag string) ([]godo.Drop
|
||||||
func (c *doCloudMockImplementation) GetAllVolumesByRegion() ([]godo.Volume, error) {
|
func (c *doCloudMockImplementation) GetAllVolumesByRegion() ([]godo.Volume, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *doCloudMockImplementation) GetVPCUUID(networkCIDR string, vpcName string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *doCloudMockImplementation) GetAllVPCs() ([]*godo.VPC, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *doCloudMockImplementation) VPCsService() godo.VPCsService {
|
||||||
|
return c.Client.VPCs
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ go_library(
|
||||||
"loadbalancer_fitask.go",
|
"loadbalancer_fitask.go",
|
||||||
"volume.go",
|
"volume.go",
|
||||||
"volume_fitask.go",
|
"volume_fitask.go",
|
||||||
|
"vpc.go",
|
||||||
|
"vpc_fitask.go",
|
||||||
],
|
],
|
||||||
importpath = "k8s.io/kops/upup/pkg/fi/cloudup/dotasks",
|
importpath = "k8s.io/kops/upup/pkg/fi/cloudup/dotasks",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@ package dotasks
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
"github.com/digitalocean/godo"
|
||||||
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
"k8s.io/kops/upup/pkg/fi"
|
"k8s.io/kops/upup/pkg/fi"
|
||||||
"k8s.io/kops/upup/pkg/fi/cloudup/do"
|
"k8s.io/kops/upup/pkg/fi/cloudup/do"
|
||||||
_ "k8s.io/kops/upup/pkg/fi/cloudup/terraform"
|
_ "k8s.io/kops/upup/pkg/fi/cloudup/terraform"
|
||||||
|
|
@ -35,14 +35,16 @@ type Droplet struct {
|
||||||
Name *string
|
Name *string
|
||||||
Lifecycle fi.Lifecycle
|
Lifecycle fi.Lifecycle
|
||||||
|
|
||||||
Region *string
|
Region *string
|
||||||
Size *string
|
Size *string
|
||||||
Image *string
|
Image *string
|
||||||
SSHKey *string
|
SSHKey *string
|
||||||
VPC *string
|
VPCUUID *string
|
||||||
Tags []string
|
NetworkCIDR *string
|
||||||
Count int
|
VPCName *string
|
||||||
UserData fi.Resource
|
Tags []string
|
||||||
|
Count int
|
||||||
|
UserData fi.Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -86,7 +88,7 @@ func (d *Droplet) Find(c *fi.Context) (*Droplet, error) {
|
||||||
Tags: foundDroplet.Tags,
|
Tags: foundDroplet.Tags,
|
||||||
SSHKey: d.SSHKey, // TODO: get from droplet or ignore change
|
SSHKey: d.SSHKey, // TODO: get from droplet or ignore change
|
||||||
UserData: d.UserData, // TODO: get from droplet or ignore change
|
UserData: d.UserData, // TODO: get from droplet or ignore change
|
||||||
VPC: fi.String(foundDroplet.VPCUUID),
|
VPCUUID: fi.String(foundDroplet.VPCUUID),
|
||||||
Lifecycle: d.Lifecycle,
|
Lifecycle: d.Lifecycle,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -147,6 +149,17 @@ func (_ *Droplet) RenderDO(t *do.DOAPITarget, a, e, changes *Droplet) error {
|
||||||
newDropletCount = expectedCount - actualCount
|
newDropletCount = expectedCount - actualCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// associate vpcuuid to the droplet if set.
|
||||||
|
vpcUUID := ""
|
||||||
|
if fi.StringValue(e.NetworkCIDR) != "" {
|
||||||
|
vpcUUID, err = t.Cloud.GetVPCUUID(fi.StringValue(e.NetworkCIDR), fi.StringValue(e.VPCName))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error fetching vpcUUID from network cidr=%s", fi.StringValue(e.NetworkCIDR))
|
||||||
|
}
|
||||||
|
} else if fi.StringValue(e.VPCUUID) != "" {
|
||||||
|
vpcUUID = fi.StringValue(e.VPCUUID)
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < newDropletCount; i++ {
|
for i := 0; i < newDropletCount; i++ {
|
||||||
_, _, err = t.Cloud.DropletsService().Create(context.TODO(), &godo.DropletCreateRequest{
|
_, _, err = t.Cloud.DropletsService().Create(context.TODO(), &godo.DropletCreateRequest{
|
||||||
Name: fi.StringValue(e.Name),
|
Name: fi.StringValue(e.Name),
|
||||||
|
|
@ -154,14 +167,13 @@ func (_ *Droplet) RenderDO(t *do.DOAPITarget, a, e, changes *Droplet) error {
|
||||||
Size: fi.StringValue(e.Size),
|
Size: fi.StringValue(e.Size),
|
||||||
Image: godo.DropletCreateImage{Slug: fi.StringValue(e.Image)},
|
Image: godo.DropletCreateImage{Slug: fi.StringValue(e.Image)},
|
||||||
Tags: e.Tags,
|
Tags: e.Tags,
|
||||||
VPCUUID: fi.StringValue(e.VPC),
|
VPCUUID: vpcUUID,
|
||||||
UserData: userData,
|
UserData: userData,
|
||||||
SSHKeys: []godo.DropletCreateSSHKey{{Fingerprint: fi.StringValue(e.SSHKey)}},
|
SSHKeys: []godo.DropletCreateSSHKey{{Fingerprint: fi.StringValue(e.SSHKey)}},
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Error creating droplet with Name=%s", fi.StringValue(e.Name))
|
return fmt.Errorf("Error creating droplet with Name=%s", fi.StringValue(e.Name))
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,9 @@ type LoadBalancer struct {
|
||||||
Region *string
|
Region *string
|
||||||
DropletTag *string
|
DropletTag *string
|
||||||
IPAddress *string
|
IPAddress *string
|
||||||
|
VPCUUID *string
|
||||||
|
VPCName *string
|
||||||
|
NetworkCIDR *string
|
||||||
ForAPIServer bool
|
ForAPIServer bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,9 +78,10 @@ func (lb *LoadBalancer) Find(c *fi.Context) (*LoadBalancer, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &LoadBalancer{
|
return &LoadBalancer{
|
||||||
Name: fi.String(loadbalancer.Name),
|
Name: fi.String(loadbalancer.Name),
|
||||||
ID: fi.String(loadbalancer.ID),
|
ID: fi.String(loadbalancer.ID),
|
||||||
Region: fi.String(loadbalancer.Region.Slug),
|
Region: fi.String(loadbalancer.Region.Slug),
|
||||||
|
VPCUUID: fi.String(loadbalancer.VPCUUID),
|
||||||
|
|
||||||
// Ignore system fields
|
// Ignore system fields
|
||||||
Lifecycle: lb.Lifecycle,
|
Lifecycle: lb.Lifecycle,
|
||||||
|
|
@ -154,17 +158,28 @@ func (_ *LoadBalancer) RenderDO(t *do.DOAPITarget, a, e, changes *LoadBalancer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// associate vpcuuid to the loadbalancer if set
|
||||||
|
vpcUUID := ""
|
||||||
|
if fi.StringValue(e.NetworkCIDR) != "" {
|
||||||
|
vpcUUID, err = t.Cloud.GetVPCUUID(fi.StringValue(e.NetworkCIDR), fi.StringValue(e.VPCName))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error fetching vpcUUID from network cidr=%s", fi.StringValue(e.NetworkCIDR))
|
||||||
|
}
|
||||||
|
} else if fi.StringValue(e.VPCUUID) != "" {
|
||||||
|
vpcUUID = fi.StringValue(e.VPCUUID)
|
||||||
|
}
|
||||||
|
|
||||||
loadBalancerService := t.Cloud.LoadBalancersService()
|
loadBalancerService := t.Cloud.LoadBalancersService()
|
||||||
loadbalancer, _, err := loadBalancerService.Create(context.TODO(), &godo.LoadBalancerRequest{
|
loadbalancer, _, err := loadBalancerService.Create(context.TODO(), &godo.LoadBalancerRequest{
|
||||||
Name: fi.StringValue(e.Name),
|
Name: fi.StringValue(e.Name),
|
||||||
Region: fi.StringValue(e.Region),
|
Region: fi.StringValue(e.Region),
|
||||||
Tag: fi.StringValue(e.DropletTag),
|
Tag: fi.StringValue(e.DropletTag),
|
||||||
|
VPCUUID: vpcUUID,
|
||||||
ForwardingRules: Rules,
|
ForwardingRules: Rules,
|
||||||
HealthCheck: HealthCheck,
|
HealthCheck: HealthCheck,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Error creating load balancer with Name=%s, Error=%v", fi.StringValue(e.Name), err)
|
return fmt.Errorf("Error creating load balancer with Name=%s, Error=%v", fi.StringValue(e.Name), err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
e.ID = fi.String(loadbalancer.ID)
|
e.ID = fi.String(loadbalancer.ID)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
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 dotasks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/digitalocean/godo"
|
||||||
|
|
||||||
|
"k8s.io/kops/upup/pkg/fi"
|
||||||
|
"k8s.io/kops/upup/pkg/fi/cloudup/do"
|
||||||
|
)
|
||||||
|
|
||||||
|
// +kops:fitask
|
||||||
|
type VPC struct {
|
||||||
|
Name *string
|
||||||
|
ID *string
|
||||||
|
Lifecycle fi.Lifecycle
|
||||||
|
IPRange *string
|
||||||
|
Region *string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fi.CompareWithID = &VPC{}
|
||||||
|
|
||||||
|
func (v *VPC) CompareWithID() *string {
|
||||||
|
return v.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VPC) Find(c *fi.Context) (*VPC, error) {
|
||||||
|
cloud := c.Cloud.(do.DOCloud)
|
||||||
|
vpcService := cloud.VPCsService()
|
||||||
|
|
||||||
|
opt := &godo.ListOptions{}
|
||||||
|
vpcs, _, err := vpcService.List(context.TODO(), opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vpc := range vpcs {
|
||||||
|
if vpc.Name == fi.StringValue(v.Name) {
|
||||||
|
return &VPC{
|
||||||
|
Name: fi.String(vpc.Name),
|
||||||
|
ID: fi.String(vpc.ID),
|
||||||
|
Lifecycle: v.Lifecycle,
|
||||||
|
IPRange: fi.String(vpc.IPRange),
|
||||||
|
Region: fi.String(vpc.RegionSlug),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VPC = nil if not found
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VPC) Run(c *fi.Context) error {
|
||||||
|
return fi.DefaultDeltaRunMethod(v, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *VPC) CheckChanges(a, e, changes *VPC) error {
|
||||||
|
if a != nil {
|
||||||
|
if changes.Name != nil {
|
||||||
|
return fi.CannotChangeField("Name")
|
||||||
|
}
|
||||||
|
if changes.ID != nil {
|
||||||
|
return fi.CannotChangeField("ID")
|
||||||
|
}
|
||||||
|
if changes.Region != nil {
|
||||||
|
return fi.CannotChangeField("Region")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if e.Name == nil {
|
||||||
|
return fi.RequiredField("Name")
|
||||||
|
}
|
||||||
|
if e.Region == nil {
|
||||||
|
return fi.RequiredField("Region")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *VPC) RenderDO(t *do.DOAPITarget, a, e, changes *VPC) error {
|
||||||
|
if a != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vpcService := t.Cloud.VPCsService()
|
||||||
|
_, _, err := vpcService.Create(context.TODO(), &godo.VPCCreateRequest{
|
||||||
|
Name: fi.StringValue(e.Name),
|
||||||
|
RegionSlug: fi.StringValue(e.Region),
|
||||||
|
IPRange: fi.StringValue(e.IPRange),
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code generated by fitask. DO NOT EDIT.
|
||||||
|
|
||||||
|
package dotasks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kops/upup/pkg/fi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VPC
|
||||||
|
|
||||||
|
var _ fi.HasLifecycle = &VPC{}
|
||||||
|
|
||||||
|
// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle
|
||||||
|
func (o *VPC) GetLifecycle() fi.Lifecycle {
|
||||||
|
return o.Lifecycle
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle
|
||||||
|
func (o *VPC) SetLifecycle(lifecycle fi.Lifecycle) {
|
||||||
|
o.Lifecycle = lifecycle
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fi.HasName = &VPC{}
|
||||||
|
|
||||||
|
// GetName returns the Name of the object, implementing fi.HasName
|
||||||
|
func (o *VPC) GetName() *string {
|
||||||
|
return o.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is the stringer function for the task, producing readable output using fi.TaskAsString
|
||||||
|
func (o *VPC) String() string {
|
||||||
|
return fi.TaskAsString(o)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue