healthcheck/healthcheck.go
// Copyright © 2016 Asteris, LLC
//
// 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 healthcheck
import (
"errors"
"github.com/asteris-llc/converge/graph"
"github.com/asteris-llc/converge/graph/node"
"github.com/asteris-llc/converge/resource"
"golang.org/x/net/context"
)
// Check defines the interface for a health check
type Check interface {
FailingDep(string, resource.TaskStatus)
HealthCheck() (*resource.HealthStatus, error)
}
// CheckGraph walks a graph and runs health checks on each health-checkable node
func CheckGraph(ctx context.Context, in *graph.Graph) (*graph.Graph, error) {
return WithNotify(ctx, in, nil)
}
// WithNotify is CheckGraph, but with notification features
func WithNotify(ctx context.Context, in *graph.Graph, notify *graph.Notifier) (*graph.Graph, error) {
return in.Transform(
ctx,
notify.Transform(func(meta *node.Node, out *graph.Graph) error {
task, err := unboxNode(meta.Value())
if err != nil {
return err
}
asCheck, ok := task.(Check)
if !ok {
return nil
}
for _, dep := range out.Dependencies(meta.ID) {
depmeta, ok := out.Get(dep)
if !ok {
continue
}
depStatus, ok := depmeta.Value().(resource.TaskStatus)
if !ok {
continue
}
if isFailure, failErr := isFailingStatus(depStatus); failErr != nil {
return failErr
} else if isFailure {
asCheck.FailingDep(dep, depStatus)
}
}
status, err := asCheck.HealthCheck()
if err != nil {
return err
}
out.Add(meta.WithValue(status))
return nil
}),
)
}
// unboxNode will remove a resource.TaskStatus from a plan.Result or apply.Result
func unboxNode(i interface{}) (resource.TaskStatus, error) {
type statusWrapper interface {
GetStatus() resource.TaskStatus
}
switch result := i.(type) {
case statusWrapper:
return result.GetStatus(), nil
case resource.TaskStatus:
return result, nil
default:
return nil, errors.New("cannot get task status from node")
}
}
func isFailingStatus(stat resource.TaskStatus) (bool, error) {
if check, ok := stat.(Check); ok {
checkStatus, err := check.HealthCheck()
if err != nil {
return true, err
}
return checkStatus.ShouldDisplay(), nil
}
return stat.HasChanges(), nil
}