Skip to content

Commit

Permalink
Extend wait_for with optional condition blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
jkabonickAtOlo authored and alekc committed Aug 24, 2024
1 parent abed1de commit 3203edc
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 6 deletions.
5 changes: 5 additions & 0 deletions internal/types/wait_for.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ package types

type WaitFor struct {
Field []WaitForField
Condition []WaitForStatusCondition
}
type WaitForField struct {
Key string
Value string
ValueType string `mapstructure:"value_type"`
}
type WaitForStatusCondition struct {
Type string
Status string
}
41 changes: 36 additions & 5 deletions kubernetes/resource_kubectl_manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,11 +460,31 @@ var (
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"condition": {
Type: schema.TypeList,
MinItems: 0,
Description: "Condition criteria for a Status Condition",
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Description: "Type as expected from the resulting Condition object",
Required: true,
},
"status": {
Type: schema.TypeString,
Description: "Status to wait for in the resulting Condition object",
Required: true,
},
},
},
},
"field": {
Type: schema.TypeList,
MinItems: 1,
MinItems: 0,
Description: "Condition criteria for a field",
Required: true,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Expand Down Expand Up @@ -633,9 +653,12 @@ func resourceKubectlManifestApply(ctx context.Context, d *schema.ResourceData, m
if err := mapstructure.Decode((v.([]interface{}))[0], &waitFor); err != nil {
return fmt.Errorf("cannot decode wait for conditions %v", err)
}
if len(waitFor.Field) == 0 && len(waitFor.Condition) == 0 {
return fmt.Errorf("at least one of `field` or `condition` must be provided in `wait_for` block")
}
log.Printf("[INFO] %v waiting for wait conditions for %vmin", manifest, timeout.Minutes())
err = resource.RetryContext(ctx, timeout,
waitForFields(ctx, restClient, waitFor.Field, manifest.GetNamespace(), manifest.GetName()))
waitForConditions(ctx, restClient, waitFor.Field, waitFor.Condition, manifest.GetNamespace(), manifest.GetName()))
if err != nil {
return err
}
Expand Down Expand Up @@ -940,7 +963,7 @@ func GetDeploymentCondition(status apps_v1.DeploymentStatus, condType apps_v1.De
return nil
}

func waitForFields(ctx context.Context, provider *RestClientResult, conditions []types.WaitForField, ns, name string) resource.RetryFunc {
func waitForConditions(ctx context.Context, provider *RestClientResult, fields []types.WaitForField, conditions []types.WaitForStatusCondition, ns, name string) resource.RetryFunc {
return func() *resource.RetryError {
rawResponse, err := provider.ResourceInterface.Get(ctx, name, meta_v1.GetOptions{})
if err != nil {
Expand All @@ -953,7 +976,7 @@ func waitForFields(ctx context.Context, provider *RestClientResult, conditions [
return resource.NonRetryableError(err)
}
gq := gojsonq.New().FromString(string(yamlJson))
for _, c := range conditions {
for _, c := range fields {
//find the key
v := gq.Reset().Find(c.Key)
if v == nil {
Expand All @@ -978,6 +1001,14 @@ func waitForFields(ctx context.Context, provider *RestClientResult, conditions [
}
}
}

for _, c := range conditions {
//find the conditions by status and type
v := gq.Reset().From("status.conditions").Where("type", "=", c.Type).Where("status", "=", c.Status)
if v == nil {
return resource.RetryableError(fmt.Errorf("key %s was not found in the resource %s", c.Status, name))
}
}
return nil
}
}
Expand Down
149 changes: 148 additions & 1 deletion kubernetes/resource_kubectl_manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,47 @@ YAML
})
}

func TestAccKubectl_WaitFor(t *testing.T) {
func TestAccKubectl_RequireWaitForFieldOrCondition(t *testing.T) {
//language=hcl
config := `
resource "kubectl_manifest" "test" {
wait_for { }
yaml_body = <<YAML
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
readinessProbe:
httpGet:
path: "/"
port: 80
initialDelaySeconds: 10
YAML
}
`

//start := time.Now()
expectedError, _ := regexp.Compile(".*at least one of `field` or `condition` must be provided in `wait_for` block.*")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckkubectlDestroy,
Steps: []resource.TestStep{
{
Config: config,
ExpectError: expectedError,
//todo: improve checking
},
},
})
}


func TestAccKubectl_WaitForField(t *testing.T) {
//language=hcl
config := `
resource "kubectl_manifest" "test" {
Expand Down Expand Up @@ -275,6 +315,113 @@ YAML
})
}


func TestAccKubectl_WaitForConditions(t *testing.T) {
//language=hcl
config := `
resource "kubectl_manifest" "test" {
wait_for {
condition {
type = "ContainersReady"
status = "True"
}
condition {
type = "Ready"
status = "True"
}
}
yaml_body = <<YAML
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
readinessProbe:
httpGet:
path: "/"
port: 80
initialDelaySeconds: 10
YAML
}
`

//start := time.Now()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckkubectlDestroy,
Steps: []resource.TestStep{
{
Config: config,
//todo: improve checking
},
},
})
}

func TestAccKubectl_WaitForFieldAndCondition(t *testing.T) {
//language=hcl
config := `
resource "kubectl_manifest" "test" {
wait_for {
condition {
type = "ContainersReady"
status = "True"
}
condition {
type = "Ready"
status = "True"
}
field {
key = "status.containerStatuses.[0].ready"
value = "true"
}
field {
key = "status.phase"
value = "Running"
}
field {
key = "status.podIP"
value = "^(\\d+(\\.|$)){4}"
value_type = "regex"
}
}
yaml_body = <<YAML
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
readinessProbe:
httpGet:
path: "/"
port: 80
initialDelaySeconds: 10
YAML
}
`

//start := time.Now()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckkubectlDestroy,
Steps: []resource.TestStep{
{
Config: config,
//todo: improve checking
},
},
})
}


//func TestAccKubect_Debug(t *testing.T) {
// //language=hcl
// config := `
Expand Down

0 comments on commit 3203edc

Please sign in to comment.