Skip to content

Commit

Permalink
Merge pull request #215 from willthames/wait
Browse files Browse the repository at this point in the history
Add wait functionality for deployments and daemonsets
  • Loading branch information
pst authored Jan 22, 2023
2 parents 07d8265 + fb9ccc7 commit 67b5b31
Show file tree
Hide file tree
Showing 13 changed files with 390 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
if: startsWith(matrix.runner, 'ubuntu-')
uses: engineerd/[email protected]
with:
version: "v0.9.0"
version: "v0.17.0"

- name: Set up Go
uses: actions/setup-go@v2
Expand Down
9 changes: 8 additions & 1 deletion docs/resources/resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ resource "kustomization_resource" "p0" {
# then loop through resources in ids_prio[1]
# and set an explicit depends_on on kustomization_resource.p0
# wait 2 minutes for any deployment or daemonset to become ready
resource "kustomization_resource" "p1" {
for_each = data.kustomization_build.test.ids_prio[1]
Expand All @@ -76,6 +77,11 @@ resource "kustomization_resource" "p1" {
? sensitive(data.kustomization_build.test.manifests[each.value])
: data.kustomization_build.test.manifests[each.value]
)
wait = true
timeouts {
create = "2m"
update = "2m"
}
depends_on = [kustomization_resource.p0]
}
Expand All @@ -99,4 +105,5 @@ resource "kustomization_resource" "p2" {
## Argument Reference

- `manifest` - (Required) JSON encoded Kubernetes resource manifest.
- 'timeouts' - (Optional) Overwrite `create` or `delete` timeout defaults. Defaults are 5 minutes for `create` and 10 minutes for `delete`.
- `wait` - Whether to wait for pods to become ready (default false). Currently only has an effect for Deployments and DaemonSets.
- 'timeouts' - (Optional) Overwrite `create`, `update` or `delete` timeout defaults. Defaults are 5 minutes for `create` and `update` and 10 minutes for `delete`.
98 changes: 98 additions & 0 deletions kustomize/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import (

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"

k8sappsv1 "k8s.io/api/apps/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
k8smeta "k8s.io/apimachinery/pkg/api/meta"
k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

k8sunstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
k8sschema "k8s.io/apimachinery/pkg/runtime/schema"
Expand All @@ -20,13 +22,20 @@ import (
"k8s.io/client-go/restmapper"
)

var waitRefreshFunctions = map[string]waitRefreshFunction{
"apps/Deployment": waitDeploymentRefresh,
"apps/Daemonset": waitDaemonsetRefresh,
}

type kManifestId struct {
group string
kind string
namespace string
name string
}

type waitRefreshFunction func(km *kManifest) (interface{}, string, error)

func mustParseProviderId(str string) *kManifestId {
kr, err := parseProviderId(str)
if err != nil {
Expand Down Expand Up @@ -354,6 +363,95 @@ func (km *kManifest) waitDeleted(t time.Duration) error {
return nil
}

func daemonsetReady(u *k8sunstructured.Unstructured) (bool, error) {
var daemonset k8sappsv1.DaemonSet
if err := k8sruntime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), &daemonset); err != nil {
return false, err
}
if daemonset.Generation == daemonset.Status.ObservedGeneration &&
daemonset.Status.UpdatedNumberScheduled == daemonset.Status.DesiredNumberScheduled &&
daemonset.Status.NumberReady == daemonset.Status.DesiredNumberScheduled &&
daemonset.Status.NumberUnavailable == 0 {
return true, nil
} else {
return false, nil
}
}

func waitDaemonsetRefresh(km *kManifest) (interface{}, string, error) {
resp, err := km.apiGet(k8smetav1.GetOptions{})
if err != nil {
if k8serrors.IsNotFound(err) {
return nil, "missing", nil
}
return nil, "error", err
}
ready, err := daemonsetReady(resp)
if err != nil {
return nil, "error", err
}
if ready {
return resp, "done", nil
}
return nil, "in progress", nil
}

func deploymentReady(u *k8sunstructured.Unstructured) (bool, error) {
var deployment k8sappsv1.Deployment
if err := k8sruntime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), &deployment); err != nil {
return false, err
}
if deployment.Generation == deployment.Status.ObservedGeneration &&
deployment.Status.AvailableReplicas == *deployment.Spec.Replicas &&
deployment.Status.AvailableReplicas == deployment.Status.Replicas &&
deployment.Status.UnavailableReplicas == 0 {
return true, nil
} else {
return false, nil
}
}

func waitDeploymentRefresh(km *kManifest) (interface{}, string, error) {
resp, err := km.apiGet(k8smetav1.GetOptions{})
if err != nil {
if k8serrors.IsNotFound(err) {
return nil, "missing", nil
}
return nil, "error", err
}
ready, err := deploymentReady(resp)
if err != nil {
return nil, "error", err
}
if ready {
return resp, "done", nil
}
return nil, "in progress", nil
}

func (km *kManifest) waitCreatedOrUpdated(t time.Duration) error {
gvk := km.gvk()
if refresh, ok := waitRefreshFunctions[fmt.Sprintf("%s/%s", gvk.Group, gvk.Kind)]; ok {
delay := 10 * time.Second
stateConf := &resource.StateChangeConf{
Target: []string{"done"},
Pending: []string{"in progress"},
Timeout: t,
Delay: delay,
NotFoundChecks: 2*int(t/delay) + 1,
Refresh: func() (interface{}, string, error) {
return refresh(km)
},
}

_, err := stateConf.WaitForState()
if err != nil {
return km.fmtErr(fmt.Errorf("timed out creating/updating %s %s/%s: %s", gvk.Kind, km.namespace(), km.name(), err))
}
}
return nil
}

func (km *kManifest) fmtErr(err error) error {
return fmt.Errorf(
"%q: %s",
Expand Down
19 changes: 19 additions & 0 deletions kustomize/resource_kustomization.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@ func kustomizationResource() *schema.Resource {
Type: schema.TypeString,
Required: true,
},
"wait": &schema.Schema{
Type: schema.TypeBool,
Default: false,
Optional: true,
},
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(5 * time.Minute),
Update: schema.DefaultTimeout(5 * time.Minute),
Delete: schema.DefaultTimeout(10 * time.Minute),
},
}
Expand Down Expand Up @@ -103,6 +109,12 @@ func kustomizationResourceCreate(d *schema.ResourceData, m interface{}) error {
return logError(err)
}

if d.Get("wait").(bool) {
if err = km.waitCreatedOrUpdated(d.Timeout(schema.TimeoutCreate)); err != nil {
return logError(err)
}
}

id := string(resp.GetUID())
d.SetId(id)

Expand Down Expand Up @@ -327,6 +339,12 @@ func kustomizationResourceUpdate(d *schema.ResourceData, m interface{}) error {
return logError(err)
}

if d.Get("wait").(bool) {
if err = kmm.waitCreatedOrUpdated(d.Timeout(schema.TimeoutUpdate)); err != nil {
return logError(err)
}
}

id := string(resp.GetUID())
d.SetId(id)

Expand Down Expand Up @@ -421,6 +439,7 @@ func kustomizationResourceImport(d *schema.ResourceData, m interface{}) ([]*sche
}

d.Set("manifest", lac)
d.Set("wait", d.Get("wait"))

return []*schema.ResourceData{d}, nil
}
Loading

0 comments on commit 67b5b31

Please sign in to comment.