<commit_before>/*
   Copyright 2020 Docker Compose CLI 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 compose

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"io"
	"strings"

	"github.com/compose-spec/compose-go/types"
	"github.com/distribution/distribution/v3/reference"
	"github.com/docker/buildx/driver"
	moby "github.com/docker/docker/api/types"
	"github.com/docker/docker/pkg/jsonmessage"
	"github.com/docker/docker/registry"
	"golang.org/x/sync/errgroup"

	"github.com/docker/compose/v2/pkg/api"
	"github.com/docker/compose/v2/pkg/progress"
)

func (s *composeService) Pull(ctx context.Context, project *types.Project, opts api.PullOptions) error {
	if opts.Quiet {
		return s.pull(ctx, project, opts)
	}
	return progress.Run(ctx, func(ctx context.Context) error {
		return s.pull(ctx, project, opts)
	})
}

func (s *composeService) pull(ctx context.Context, project *types.Project, opts api.PullOptions) error {
	info, err := s.apiClient().Info(ctx)
	if err != nil {
		return err
	}

	if info.IndexServerAddress == "" {
		info.IndexServerAddress = registry.IndexServer
	}

	w := progress.ContextWriter(ctx)
	eg, ctx := errgroup.WithContext(ctx)

	var mustBuild []string
	for _, service := range project.Services {
		service := service
		if service.Image == "" {
			w.Event(progress.Event{
				ID:     service.Name,
				Status: progress.Done,
				Text:   "Skipped",
			})
			continue
		}
		eg.Go(func() error {
			err := s.pullServiceImage(ctx, service, info, s.configFile(), w, false)
			if err != nil {
				if !opts.IgnoreFailures {
					if service.Build != nil {
						mustBuild = append(mustBuild, service.Name)
					}
					return err
				}
				w.TailMsgf("Pulling %s: %s", service.Name, err.Error())
			}
			return nil
		})
	}

	err = eg.Wait()

	if !opts.IgnoreFailures && len(mustBuild) > 0 {
		w.TailMsgf("WARNING: Some service image(s) must be built from source by running:\n    docker compose build %s", strings.Join(mustBuild, " "))
	}

	return err
}

func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPull bool) error {
	w.Event(progress.Event{
		ID:     service.Name,
		Status: progress.Working,
		Text:   "Pulling",
	})
	ref, err := reference.ParseNormalizedNamed(service.Image)
	if err != nil {
		return err
	}

	repoInfo, err := registry.ParseRepositoryInfo(ref)
	if err != nil {
		return err
	}

	key := repoInfo.Index.Name
	if repoInfo.Index.Official {
		key = info.IndexServerAddress
	}

	authConfig, err := configFile.GetAuthConfig(key)
	if err != nil {
		return err
	}

	buf, err := json.Marshal(authConfig)
	if err != nil {
		return err
	}

	stream, err := s.apiClient().ImagePull(ctx, service.Image, moby.ImagePullOptions{
		RegistryAuth: base64.URLEncoding.EncodeToString(buf),
		Platform:     service.Platform,
	})
	if err != nil {
		w.Event(progress.Event{
			ID:     service.Name,
			Status: progress.Error,
			Text:   "Error",
		})
		return WrapCategorisedComposeError(err, PullFailure)
	}

	dec := json.NewDecoder(stream)
	for {
		var jm jsonmessage.JSONMessage
		if err := dec.Decode(&jm); err != nil {
			if err == io.EOF {
				break
			}
			return WrapCategorisedComposeError(err, PullFailure)
		}
		if jm.Error != nil {
			return WrapCategorisedComposeError(errors.New(jm.Error.Message), PullFailure)
		}
		if !quietPull {
			toPullProgressEvent(service.Name, jm, w)
		}
	}
	w.Event(progress.Event{
		ID:     service.Name,
		Status: progress.Done,
		Text:   "Pulled",
	})
	return nil
}

func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]string, quietPull bool) error {
	info, err := s.apiClient().Info(ctx)
	if err != nil {
		return err
	}

	if info.IndexServerAddress == "" {
		info.IndexServerAddress = registry.IndexServer
	}

	var needPull []types.ServiceConfig
	for _, service := range project.Services {
		if service.Image == "" {
			continue
		}
		switch service.PullPolicy {
		case "", types.PullPolicyMissing, types.PullPolicyIfNotPresent:
			if _, ok := images[service.Image]; ok {
				continue
			}
		case types.PullPolicyNever, types.PullPolicyBuild:
			continue
		case types.PullPolicyAlways:
			// force pull
		}
		needPull = append(needPull, service)
	}
	if len(needPull) == 0 {
		return nil
	}

	return progress.Run(ctx, func(ctx context.Context) error {
		w := progress.ContextWriter(ctx)
		eg, ctx := errgroup.WithContext(ctx)
		for _, service := range needPull {
			service := service
			eg.Go(func() error {
				err := s.pullServiceImage(ctx, service, info, s.configFile(), w, quietPull)
				if err != nil && service.Build != nil {
					// image can be built, so we can ignore pull failure
					return nil
				}
				return err
			})
		}
		return eg.Wait()
	})
}

func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, w progress.Writer) {
	if jm.ID == "" || jm.Progress == nil {
		return
	}

	var (
		text   string
		status = progress.Working
	)

	text = jm.Progress.String()

	if jm.Status == "Pull complete" ||
		jm.Status == "Already exists" ||
		strings.Contains(jm.Status, "Image is up to date") ||
		strings.Contains(jm.Status, "Downloaded newer image") {
		status = progress.Done
	}

	if jm.Error != nil {
		status = progress.Error
		text = jm.Error.Message
	}

	w.Event(progress.Event{
		ID:         jm.ID,
		ParentID:   parent,
		Text:       jm.Status,
		Status:     status,
		StatusText: text,
	})
}
<commit_msg>`pull` to respect pull_policy<commit_after>/*
   Copyright 2020 Docker Compose CLI 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 compose

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"io"
	"strings"

	"github.com/compose-spec/compose-go/types"
	"github.com/distribution/distribution/v3/reference"
	"github.com/docker/buildx/driver"
	moby "github.com/docker/docker/api/types"
	"github.com/docker/docker/pkg/jsonmessage"
	"github.com/docker/docker/registry"
	"golang.org/x/sync/errgroup"

	"github.com/docker/compose/v2/pkg/api"
	"github.com/docker/compose/v2/pkg/progress"
)

func (s *composeService) Pull(ctx context.Context, project *types.Project, opts api.PullOptions) error {
	if opts.Quiet {
		return s.pull(ctx, project, opts)
	}
	return progress.Run(ctx, func(ctx context.Context) error {
		return s.pull(ctx, project, opts)
	})
}

func (s *composeService) pull(ctx context.Context, project *types.Project, opts api.PullOptions) error {
	info, err := s.apiClient().Info(ctx)
	if err != nil {
		return err
	}

	if info.IndexServerAddress == "" {
		info.IndexServerAddress = registry.IndexServer
	}

	images, err := s.getLocalImagesDigests(ctx, project)
	if err != nil {
		return err
	}

	w := progress.ContextWriter(ctx)
	eg, ctx := errgroup.WithContext(ctx)

	var mustBuild []string
	for _, service := range project.Services {
		service := service
		if service.Image == "" {
			w.Event(progress.Event{
				ID:     service.Name,
				Status: progress.Done,
				Text:   "Skipped",
			})
			continue
		}

		switch service.PullPolicy {
		case types.PullPolicyNever, types.PullPolicyBuild:
			w.Event(progress.Event{
				ID:     service.Name,
				Status: progress.Done,
				Text:   "Skipped",
			})
			continue
		case types.PullPolicyMissing, types.PullPolicyIfNotPresent:
			if _, ok := images[service.Image]; ok {
				w.Event(progress.Event{
					ID:     service.Name,
					Status: progress.Done,
					Text:   "Exists",
				})
				continue
			}
		}

		eg.Go(func() error {
			err := s.pullServiceImage(ctx, service, info, s.configFile(), w, false)
			if err != nil {
				if !opts.IgnoreFailures {
					if service.Build != nil {
						mustBuild = append(mustBuild, service.Name)
					}
					return err
				}
				w.TailMsgf("Pulling %s: %s", service.Name, err.Error())
			}
			return nil
		})
	}

	err = eg.Wait()

	if !opts.IgnoreFailures && len(mustBuild) > 0 {
		w.TailMsgf("WARNING: Some service image(s) must be built from source by running:\n    docker compose build %s", strings.Join(mustBuild, " "))
	}

	return err
}

func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer, quietPull bool) error {
	w.Event(progress.Event{
		ID:     service.Name,
		Status: progress.Working,
		Text:   "Pulling",
	})
	ref, err := reference.ParseNormalizedNamed(service.Image)
	if err != nil {
		return err
	}

	repoInfo, err := registry.ParseRepositoryInfo(ref)
	if err != nil {
		return err
	}

	key := repoInfo.Index.Name
	if repoInfo.Index.Official {
		key = info.IndexServerAddress
	}

	authConfig, err := configFile.GetAuthConfig(key)
	if err != nil {
		return err
	}

	buf, err := json.Marshal(authConfig)
	if err != nil {
		return err
	}

	stream, err := s.apiClient().ImagePull(ctx, service.Image, moby.ImagePullOptions{
		RegistryAuth: base64.URLEncoding.EncodeToString(buf),
		Platform:     service.Platform,
	})
	if err != nil {
		w.Event(progress.Event{
			ID:     service.Name,
			Status: progress.Error,
			Text:   "Error",
		})
		return WrapCategorisedComposeError(err, PullFailure)
	}

	dec := json.NewDecoder(stream)
	for {
		var jm jsonmessage.JSONMessage
		if err := dec.Decode(&jm); err != nil {
			if err == io.EOF {
				break
			}
			return WrapCategorisedComposeError(err, PullFailure)
		}
		if jm.Error != nil {
			return WrapCategorisedComposeError(errors.New(jm.Error.Message), PullFailure)
		}
		if !quietPull {
			toPullProgressEvent(service.Name, jm, w)
		}
	}
	w.Event(progress.Event{
		ID:     service.Name,
		Status: progress.Done,
		Text:   "Pulled",
	})
	return nil
}

func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]string, quietPull bool) error {
	info, err := s.apiClient().Info(ctx)
	if err != nil {
		return err
	}

	if info.IndexServerAddress == "" {
		info.IndexServerAddress = registry.IndexServer
	}

	var needPull []types.ServiceConfig
	for _, service := range project.Services {
		if service.Image == "" {
			continue
		}
		switch service.PullPolicy {
		case "", types.PullPolicyMissing, types.PullPolicyIfNotPresent:
			if _, ok := images[service.Image]; ok {
				continue
			}
		case types.PullPolicyNever, types.PullPolicyBuild:
			continue
		case types.PullPolicyAlways:
			// force pull
		}
		needPull = append(needPull, service)
	}
	if len(needPull) == 0 {
		return nil
	}

	return progress.Run(ctx, func(ctx context.Context) error {
		w := progress.ContextWriter(ctx)
		eg, ctx := errgroup.WithContext(ctx)
		for _, service := range needPull {
			service := service
			eg.Go(func() error {
				err := s.pullServiceImage(ctx, service, info, s.configFile(), w, quietPull)
				if err != nil && service.Build != nil {
					// image can be built, so we can ignore pull failure
					return nil
				}
				return err
			})
		}
		return eg.Wait()
	})
}

func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, w progress.Writer) {
	if jm.ID == "" || jm.Progress == nil {
		return
	}

	var (
		text   string
		status = progress.Working
	)

	text = jm.Progress.String()

	if jm.Status == "Pull complete" ||
		jm.Status == "Already exists" ||
		strings.Contains(jm.Status, "Image is up to date") ||
		strings.Contains(jm.Status, "Downloaded newer image") {
		status = progress.Done
	}

	if jm.Error != nil {
		status = progress.Error
		text = jm.Error.Message
	}

	w.Event(progress.Event{
		ID:         jm.ID,
		ParentID:   parent,
		Text:       jm.Status,
		Status:     status,
		StatusText: text,
	})
}
<|endoftext|>


<commit_before>package aws

import (
	"errors"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func dataSourceAwsEc2TransitGatewayRouteTable() *schema.Resource {
	return &schema.Resource{
		Read: dataSourceAwsEc2TransitGatewayRouteTableRead,

		Schema: map[string]*schema.Schema{
			"default_association_route_table": {
				Type:     schema.TypeBool,
				Computed: true,
			},
			"default_propagation_route_table": {
				Type:     schema.TypeBool,
				Computed: true,
			},
			"filter": dataSourceFiltersSchema(),
			"id": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"transit_gateway_id": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"tags": tagsSchemaComputed(),
		},
	}
}

func dataSourceAwsEc2TransitGatewayRouteTableRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	input := &ec2.DescribeTransitGatewayRouteTablesInput{}

	if v, ok := d.GetOk("filter"); ok {
		input.Filters = buildAwsDataSourceFilters(v.(*schema.Set))
	}

	if v, ok := d.GetOk("id"); ok {
		input.TransitGatewayRouteTableIds = []*string{aws.String(v.(string))}
	}

	log.Printf("[DEBUG] Reading EC2 Transit Gateways: %s", input)
	output, err := conn.DescribeTransitGatewayRouteTables(input)

	if err != nil {
		return fmt.Errorf("error reading EC2 Transit Gateway Route Table: %s", err)
	}

	if output == nil || len(output.TransitGatewayRouteTables) == 0 {
		return errors.New("error reading EC2 Transit Gateway Route Table: no results found")
	}

	if len(output.TransitGatewayRouteTables) > 1 {
		return errors.New("error reading EC2 Transit Gateway Route Table: multiple results found, try adjusting search criteria")
	}

	transitGatewayRouteTable := output.TransitGatewayRouteTables[0]

	if transitGatewayRouteTable == nil {
		return errors.New("error reading EC2 Transit Gateway Route Table: empty result")
	}

	d.Set("default_association_route_table", aws.BoolValue(transitGatewayRouteTable.DefaultAssociationRouteTable))
	d.Set("default_propagation_route_table", aws.BoolValue(transitGatewayRouteTable.DefaultPropagationRouteTable))

	if err := d.Set("tags", tagsToMap(transitGatewayRouteTable.Tags)); err != nil {
		return fmt.Errorf("error setting tags: %s", err)
	}

	d.Set("transit_gateway_id", aws.StringValue(transitGatewayRouteTable.TransitGatewayId))

	d.SetId(aws.StringValue(transitGatewayRouteTable.TransitGatewayRouteTableId))

	return nil
}
<commit_msg>d/aws_ec2_transit_gateway_route_table: Refactor tagging logic to keyvaluetags package.<commit_after>package aws

import (
	"errors"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
	"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
)

func dataSourceAwsEc2TransitGatewayRouteTable() *schema.Resource {
	return &schema.Resource{
		Read: dataSourceAwsEc2TransitGatewayRouteTableRead,

		Schema: map[string]*schema.Schema{
			"default_association_route_table": {
				Type:     schema.TypeBool,
				Computed: true,
			},
			"default_propagation_route_table": {
				Type:     schema.TypeBool,
				Computed: true,
			},
			"filter": dataSourceFiltersSchema(),
			"id": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"transit_gateway_id": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"tags": tagsSchemaComputed(),
		},
	}
}

func dataSourceAwsEc2TransitGatewayRouteTableRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ec2conn

	input := &ec2.DescribeTransitGatewayRouteTablesInput{}

	if v, ok := d.GetOk("filter"); ok {
		input.Filters = buildAwsDataSourceFilters(v.(*schema.Set))
	}

	if v, ok := d.GetOk("id"); ok {
		input.TransitGatewayRouteTableIds = []*string{aws.String(v.(string))}
	}

	log.Printf("[DEBUG] Reading EC2 Transit Gateways: %s", input)
	output, err := conn.DescribeTransitGatewayRouteTables(input)

	if err != nil {
		return fmt.Errorf("error reading EC2 Transit Gateway Route Table: %s", err)
	}

	if output == nil || len(output.TransitGatewayRouteTables) == 0 {
		return errors.New("error reading EC2 Transit Gateway Route Table: no results found")
	}

	if len(output.TransitGatewayRouteTables) > 1 {
		return errors.New("error reading EC2 Transit Gateway Route Table: multiple results found, try adjusting search criteria")
	}

	transitGatewayRouteTable := output.TransitGatewayRouteTables[0]

	if transitGatewayRouteTable == nil {
		return errors.New("error reading EC2 Transit Gateway Route Table: empty result")
	}

	d.Set("default_association_route_table", aws.BoolValue(transitGatewayRouteTable.DefaultAssociationRouteTable))
	d.Set("default_propagation_route_table", aws.BoolValue(transitGatewayRouteTable.DefaultPropagationRouteTable))

	if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(transitGatewayRouteTable.Tags).IgnoreAws().Map()); err != nil {
		return fmt.Errorf("error setting tags: %s", err)
	}

	d.Set("transit_gateway_id", aws.StringValue(transitGatewayRouteTable.TransitGatewayId))

	d.SetId(aws.StringValue(transitGatewayRouteTable.TransitGatewayRouteTableId))

	return nil
}
<|endoftext|>


<commit_before>package datadog

import (
	"bytes"
	"fmt"
	"log"
	"strconv"
	"time"
	"strings"

	"github.com/zorkian/go-datadog-api"

	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/hashcode"
)

// Work around the nested struct in https://github.com/zorkian/go-datadog-api/blob/master/dashboards.go#L16
type GraphDefintionRequests struct {
	Query   string `json:"q"`
	Stacked bool   `json:"stacked"`
}

func resourceDatadogGraph() *schema.Resource {
	return &schema.Resource{
		Create: resourceDatadogGraphCreate,
		Exists: resourceDatadogGraphExists,
		Read:   resourceDatadogGraphRead,
		Delete: resourceDatadogGraphDelete,
		Update: resourceDatadogGraphUpdate,

		Schema: map[string]*schema.Schema{
			"dashboard_id": &schema.Schema{
				Type:     schema.TypeInt,
				//Computed: true,
				Required: true,
				ForceNew: true,
			},
			"title": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"viz": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"request": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"query": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"stacked": &schema.Schema{
							Type:     schema.TypeBool,
							Required: true,
						},
					},

				},
				Set: resourceDatadogRequestHash,
			},

			// TODO: support events.
		},
	}
}

func resourceDatadogGraphCreate(d *schema.ResourceData, meta interface{}) error {
	// This should create graphs associated with dashboards.
	// it's a virtual resource, a la "resource_vpn_connection_route"
	// hence we will need to do a bit of hacking to find out what dashboard.

	// TODO: Delete placeholder graph. See https://github.com/ojongerius/terraform-provider-datadog/issues/8

	resourceDatadogGraphUpdate(d, meta)

	Id := int(time.Now().Unix())

	d.SetId(strconv.Itoa(Id)) // Use seconds since Epoch, needs to be a string when saving.

	log.Printf("[INFO] Dashboard ID: %d", Id)

	err := resourceDatadogGraphRetrieve(d, meta)

	if err != nil {
		return err
	}

	return nil
}

func resourceDatadogGraphExists(d *schema.ResourceData, meta interface{}) (bool, error) {
	client := meta.(*datadog.Client)

	// Verify our Dashboard(s) exist
	_, err := client.GetDashboard(d.Get("dashboard_id").(int))

	if err != nil {
		if strings.EqualFold(err.Error(), "API error: 404 Not Found") {
			return false, nil
		}

		return false, fmt.Errorf("Error retrieving dashboard: %s", err)
	}

	// Verify we exist
	err = resourceDatadogGraphRead(d, meta)

	if err != nil {
		return false, err
	}

	return true, nil
}

func resourceDatadogGraphRead(d *schema.ResourceData, meta interface{}) error {
	err := resourceDatadogGraphRetrieve(d, meta)

	if err != nil {
		return err
	}

	return nil
}

func resourceDatadogGraphRetrieve(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*datadog.Client)

	// Get the dashboard(s)
	dashboard, err := client.GetDashboard(d.Get("dashboard_id").(int))

	if err != nil {
		return fmt.Errorf("Error retrieving associated dashboard: %s", err)
	}

	// Walk through the graphs
	for _, g := range dashboard.Graphs {
		// TODO: Using the title as unique identifier is 'suboptimal'. Interested in different strategies.
		if g.Title == d.Get("title") {
			log.Printf("[DEBUG] Found matching title. Start setting/saving state.")
			d.Set("dashboard_id", d.Get("dashboard_id"))
			d.Set("title", g.Title)
			d.Set("viz", g.Definition.Viz)

			// Create an empty schema to hold all the requests.
			request := &schema.Set{F: resourceDatadogRequestHash}

			for _, r := range g.Definition.Requests {
				m := make(map[string]interface{})

				if r.Query != "" {
					m["query"] = r.Query
				}

				m["stacked"] = r.Stacked

				request.Add(m)
			}

			d.Set("request", request)

			return nil

		}
	}

	// If we are still around we've not found ourselves. Set SetId to empty so Terraform will create the resource for us.
	d.SetId("")

	return nil
}

func resourceDatadogGraphUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*datadog.Client)

	// Get the dashboard
	dashboard, err := client.GetDashboard(d.Get("dashboard_id").(int))

	if err != nil {
		return err
	}

	log.Printf("[DEBUG] dashboard before added graph: %#v", dashboard)

	log.Printf("[DEBUG] Checking if requests have changed.")

	// Check if there are changes
	if d.HasChange("request") {

		graph_definition := datadog.Graph{}.Definition
		graph_requests := datadog.Graph{}.Definition.Requests
		graph_definition.Viz = d.Get("viz").(string)

		log.Printf("[DEBUG] Request has changed.")
		o, n := d.GetChange("request")
		ors := o.(*schema.Set).Difference(n.(*schema.Set))
		nrs := n.(*schema.Set).Difference(o.(*schema.Set))

		// Loop through all the old requests and delete any obsolete ones
		for _, request := range ors.List() {
			m := request.(map[string]interface{})

			// TODO: implement
			// Delete the query as it no longer exists in the config
			log.Printf("[DEBUG] Deleting graph query %s", m["query"].(string))
			log.Printf("[DEBUG] Deleting graph stacked %t", m["stacked"].(bool))

		}
		// Loop through all the new requests and append them
		for _, request := range nrs.List() {
			m := request.(map[string]interface{})

			// Add the request
			log.Printf("[DEBUG] Adding graph query %s", m["query"].(string))
			log.Printf("[DEBUG] Adding graph stacked %t", m["stacked"].(bool))
			graph_requests = append(graph_requests, GraphDefintionRequests{Query: m["query"].(string),
				Stacked: m["stacked"].(bool)})
		}

		// Add requests to the graph definition
		graph_definition.Requests = graph_requests
		the_graph := datadog.Graph{Title: d.Get("title").(string), Definition: graph_definition}

		dashboard.Graphs = append(dashboard.Graphs, the_graph) // Should be done for each

		log.Printf("[DEBUG] dashboard after adding graph: %#v", dashboard)

		// Update/commit
		err = client.UpdateDashboard(dashboard)

		if err != nil {
			return err
		}
	} else {
		log.Printf("[DEBUG] No changes detected, nothing to do here.")
	}

	return nil

	// TODO: still need this?
	//return resourceDatadogGraphRead(d, meta)
}

func resourceDatadogGraphDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*datadog.Client)

	// Get the dashboard
	dashboard, err := client.GetDashboard(d.Get("dashboard_id").(int))

	if err != nil {
		return fmt.Errorf("Error retrieving associated dashboard: %s", err)
	}

	// Build a new slice of graphs, without the nominee to deleted.
	// TODO: Use the set for this.
	new_graphs := []datadog.Graph{}
	for _, r := range dashboard.Graphs {
		// TODO: efficiently test if the are the same for this POC we'll just match on title
		if r.Title == d.Get("title") {
			continue
		} else {
			new_graphs = append(new_graphs, r)
		}
	}

	dashboard.Graphs = new_graphs

	// Update/commit
	err = client.UpdateDashboard(dashboard)

	if err != nil {
		return err
	}

	err = resourceDatadogGraphRetrieve(d, meta)

	if err != nil {
		return err
	}

	return nil
}

func resourceDatadogRequestHash(v interface{}) int{
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	if v, ok := m["query"];  ok {
		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
	}

	if v, ok := m["stacked"];  ok {
		buf.WriteString(fmt.Sprintf("%t-", v.(bool)))
	}

	return hashcode.String(buf.String())
}
<commit_msg>Add ID to title to work around the fact that Graphs do not have IDs at Datadog. Tests pass, need to update comments and polish a little.<commit_after>package datadog

import (
	"bytes"
	"fmt"
	"log"
	"strconv"
	"time"
	"strings"

	"github.com/zorkian/go-datadog-api"

	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/hashcode"
)

// Work around the nested struct in https://github.com/zorkian/go-datadog-api/blob/master/dashboards.go#L16
type GraphDefintionRequests struct {
	Query   string `json:"q"`
	Stacked bool   `json:"stacked"`
}

func resourceDatadogGraph() *schema.Resource {
	return &schema.Resource{
		Create: resourceDatadogGraphCreate,
		Exists: resourceDatadogGraphExists,
		Read:   resourceDatadogGraphRead,
		Delete: resourceDatadogGraphDelete,
		Update: resourceDatadogGraphUpdate,

		Schema: map[string]*schema.Schema{
			"dashboard_id": &schema.Schema{
				Type:     schema.TypeInt,
				//Computed: true,
				Required: true,
				ForceNew: true,
			},
			"title": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"viz": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"request": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"query": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"stacked": &schema.Schema{
							Type:     schema.TypeBool,
							Required: true,
						},
					},

				},
				Set: resourceDatadogRequestHash,
			},

			// TODO: support events.
		},
	}
}

func resourceDatadogGraphCreate(d *schema.ResourceData, meta interface{}) error {
	// This should create graphs associated with dashboards.
	// it's a virtual resource, a la "resource_vpn_connection_route"
	// hence we will need to do a bit of hacking to find out what dashboard.

	// TODO: Delete placeholder graph. See https://github.com/ojongerius/terraform-provider-datadog/issues/8

	// TODO:
	// * In Create; use as ID, the hash of the whole graph.
	// * When matching in Read/Retrieve; use this hash to see if we found it (yes this is relatively resource intense)
	// * This does imply that we *must* delete all graphs that are *not* know to us. How do we pull that off?
	//                    ^^ do we do that by re-posting all graphs on an update?
	//                    ^^ this is tricky. The graph resources do not know about others graphs, so we will just not
	//                       find ourselves...
	//                       The trick used by Terraform is for route tables and routes in it. Which is a different case.
	// * Profit
	//
	// New approach:
    //
	// ID in title.
	//
	// Difficulty; we can't expect the user to have the ID in their description
	// 		       but they change detection needs be so that we add be aware of this -Use the ID to identify
	//                 remove the ID of the diff.
	//
	// * Change Read function so find it by the ID, but store the title without the ID
	// * Change Update function to append the ID when updating the Dashboard at DD

	if d.Id() == "" {
		Id := int(time.Now().Unix())
		d.SetId(strconv.Itoa(Id)) // Use seconds since Epoch, needs to be a string when saving.

		log.Printf("[INFO] Graph ID: %d", Id)
	}

	// TODO: swapped this around so Id is avail
	resourceDatadogGraphUpdate(d, meta)

	err := resourceDatadogGraphRetrieve(d, meta)

	if err != nil {
		return err
	}

	return nil
}

func resourceDatadogGraphExists(d *schema.ResourceData, meta interface{}) (bool, error) {
	client := meta.(*datadog.Client)

	// Verify our Dashboard(s) exist
	_, err := client.GetDashboard(d.Get("dashboard_id").(int))

	if err != nil {
		if strings.EqualFold(err.Error(), "API error: 404 Not Found") {
			return false, nil
		}

		return false, fmt.Errorf("Error retrieving dashboard: %s", err)
	}

	// Verify we exist
	err = resourceDatadogGraphRead(d, meta)

	if err != nil {
		return false, err
	}

	return true, nil
}

func resourceDatadogGraphRead(d *schema.ResourceData, meta interface{}) error {
	err := resourceDatadogGraphRetrieve(d, meta)

	if err != nil {
		return err
	}

	return nil
}

func resourceDatadogGraphRetrieve(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*datadog.Client)

	// Get the dashboard(s)
	dashboard, err := client.GetDashboard(d.Get("dashboard_id").(int))

	if err != nil {
		return fmt.Errorf("Error retrieving associated dashboard: %s", err)
	}

	// Walk through the graphs
	for _, g := range dashboard.Graphs {
		// If it ends with our ID, go:
		if strings.HasSuffix(g.Title, fmt.Sprintf("(%s)", d.Id())){
			log.Printf("[DEBUG] Found matching title. Start setting/saving state.")
			d.Set("dashboard_id", d.Get("dashboard_id"))
			// Save title to state, without the ID
			d.Set("title", strings.Replace(g.Title, fmt.Sprintf(" (%s)", d.Id()), "", 1))
			d.Set("viz", g.Definition.Viz)

			// Create an empty schema to hold all the requests.
			request := &schema.Set{F: resourceDatadogRequestHash}

			for _, r := range g.Definition.Requests {
				m := make(map[string]interface{})

				if r.Query != "" {
					m["query"] = r.Query
				}

				m["stacked"] = r.Stacked

				request.Add(m)
			}

			d.Set("request", request)

			return nil

		}
	}

	// If we are still around we've not found ourselves. Set SetId to empty so Terraform will create the resource for us.
	d.SetId("")

	return nil
}

func resourceDatadogGraphUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*datadog.Client)

	// Get the dashboard
	dashboard, err := client.GetDashboard(d.Get("dashboard_id").(int))

	if err != nil {
		return err
	}

	// Check if there are changes
	if d.HasChange("request") {
		graph_definition := datadog.Graph{}.Definition
		graph_requests := datadog.Graph{}.Definition.Requests
		graph_definition.Viz = d.Get("viz").(string)

		log.Printf("[DEBUG] Request has changed.")
		o, n := d.GetChange("request")
		ors := o.(*schema.Set).Difference(n.(*schema.Set))
		nrs := n.(*schema.Set).Difference(o.(*schema.Set))

		// Loop through all the old requests and delete any obsolete ones
		for _, request := range ors.List() {
			m := request.(map[string]interface{})

			// TODO: implement
			// Delete the query as it no longer exists in the config
			log.Printf("[DEBUG] Deleting graph query %s", m["query"].(string))
			log.Printf("[DEBUG] Deleting graph stacked %t", m["stacked"].(bool))

		}
		// Loop through all the new requests and append them
		for _, request := range nrs.List() {
			m := request.(map[string]interface{})

			// Add the request
			log.Printf("[DEBUG] Adding graph query %s", m["query"].(string))
			log.Printf("[DEBUG] Adding graph stacked %t", m["stacked"].(bool))
			graph_requests = append(graph_requests, GraphDefintionRequests{Query: m["query"].(string),
				Stacked: m["stacked"].(bool)})
		}

		// Add requests to the graph definition
		graph_definition.Requests = graph_requests
		title := d.Get("title").(string) + fmt.Sprintf(" (%s)", d.Id())
		the_graph := datadog.Graph{Title: title, Definition: graph_definition}

		dashboard.Graphs = append(dashboard.Graphs, the_graph) // Should be done for each
	}

	// Update/commit
	err = client.UpdateDashboard(dashboard)

	if err != nil {
		return err
	}

	return nil
}

func resourceDatadogGraphDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*datadog.Client)

	// Get the dashboard
	dashboard, err := client.GetDashboard(d.Get("dashboard_id").(int))

	if err != nil {
		return fmt.Errorf("Error retrieving associated dashboard: %s", err)
	}

	// Build a new slice of graphs, without the nominee to deleted.
	// TODO: Use the set for this.
	new_graphs := []datadog.Graph{}
	for _, r := range dashboard.Graphs {
		// TODO: Look for our ID in the title (what is the most efficient way in Golang?)
		if strings.HasSuffix(r.Title, fmt.Sprintf("(%s)", d.Id())) {
			//if r.Title == d.Get("title") {
			continue
		} else {
			new_graphs = append(new_graphs, r)
		}
	}

	dashboard.Graphs = new_graphs

	// Update/commit
	err = client.UpdateDashboard(dashboard)

	if err != nil {
		return err
	}

	err = resourceDatadogGraphRetrieve(d, meta)

	if err != nil {
		return err
	}

	return nil
}

func resourceDatadogRequestHash(v interface{}) int{
	var buf bytes.Buffer
	m := v.(map[string]interface{})

	if v, ok := m["query"];  ok {
		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
	}

	if v, ok := m["stacked"];  ok {
		buf.WriteString(fmt.Sprintf("%t-", v.(bool)))
	}

	return hashcode.String(buf.String())
}
<|endoftext|>


<commit_before>//  Copyright (c) 2017 Rackspace
//
//  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 middleware

import (
	"io"
	"net/http"

	"github.com/troubling/hummingbird/common/srv"
	"go.uber.org/zap"
)

type PipeResponse struct {
}

type PipeResponseWriter struct {
	w      *io.PipeWriter
	status int
	header http.Header
	done   chan bool
	Logger srv.LowLevelLogger
}

func (w *PipeResponseWriter) Write(stuff []byte) (int, error) {
	written, err := w.w.Write(stuff)
	if err != nil {
		w.Logger.Error("PipeResponseWriter Write() error", zap.Error(err))
	}
	return written, err
}

func (w *PipeResponseWriter) Header() http.Header {
	return w.header
}

func (w *PipeResponseWriter) WriteHeader(status int) {
	w.status = status
	w.done <- true
}

func (w *PipeResponseWriter) Close() {
	w.w.Close()
}

func NewPipeResponseWriter(writer *io.PipeWriter, done chan bool, logger srv.LowLevelLogger) *PipeResponseWriter {
	header := make(map[string][]string)
	return &PipeResponseWriter{
		w:      writer,
		header: header,
		done:   done,
		Logger: logger,
	}
}

func (p *PipeResponse) Get(path string, request *http.Request, source string, auth AuthorizeFunc) (io.ReadCloser, http.Header, int) {
	ctx := GetProxyContext(request)
	subRequest, err := ctx.newSubrequest("GET", path, nil, request, source)
	if err != nil {
		ctx.Logger.Error("getSourceObject GET error", zap.Error(err))
		return nil, nil, 400
	}
	if request.URL.Query().Get("multipart-manifest") == "get" {
		subRequest.URL.RawQuery = "multipart-manifest=get&format=raw"
	}
	CopyItems(subRequest.Header, request.Header)
	// FIXME. Are we going to do X-Newest?
	subRequest.Header.Set("X-Newest", "true")
	subRequest.Header.Del("X-Backend-Storage-Policy-Index")

	if auth != nil {
		GetProxyContext(subRequest).Authorize = auth
	}

	pipeReader, pipeWriter := io.Pipe()
	done := make(chan bool)
	writer := NewPipeResponseWriter(pipeWriter, done, ctx.Logger)
	go func() {
		defer writer.Close()
		ctx.serveHTTPSubrequest(writer, subRequest)
	}()
	<-done

	return pipeReader, writer.Header(), writer.status
}
<commit_msg>Broke this rebasing<commit_after>//  Copyright (c) 2017 Rackspace
//
//  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 middleware

import (
	"io"
	"net/http"
	"strings"

	"github.com/troubling/hummingbird/common/srv"
	"go.uber.org/zap"
)

type PipeResponse struct {
}

type PipeResponseWriter struct {
	w      *io.PipeWriter
	status int
	header http.Header
	done   chan bool
	Logger srv.LowLevelLogger
}

func (w *PipeResponseWriter) Write(stuff []byte) (int, error) {
	written, err := w.w.Write(stuff)
	if err != nil {
		if !strings.Contains(err.Error(), "closed pipe") {
			w.Logger.Error("PipeResponseWriter Write() error", zap.Error(err))
		}
	}
	return written, err
}

func (w *PipeResponseWriter) Header() http.Header {
	return w.header
}

func (w *PipeResponseWriter) WriteHeader(status int) {
	w.status = status
	w.done <- true
}

func (w *PipeResponseWriter) Close() {
	w.w.Close()
}

func NewPipeResponseWriter(writer *io.PipeWriter, done chan bool, logger srv.LowLevelLogger) *PipeResponseWriter {
	header := make(map[string][]string)
	return &PipeResponseWriter{
		w:      writer,
		header: header,
		done:   done,
		Logger: logger,
	}
}

func (p *PipeResponse) Get(path string, request *http.Request, source string, auth AuthorizeFunc) (io.ReadCloser, http.Header, int) {
	ctx := GetProxyContext(request)
	subRequest, err := ctx.newSubrequest("GET", path, nil, request, source)
	if err != nil {
		ctx.Logger.Error("getSourceObject GET error", zap.Error(err))
		return nil, nil, 400
	}
	if request.URL.Query().Get("multipart-manifest") == "get" {
		subRequest.URL.RawQuery = "multipart-manifest=get&format=raw"
	}
	CopyItems(subRequest.Header, request.Header)
	// FIXME. Are we going to do X-Newest?
	subRequest.Header.Set("X-Newest", "true")
	subRequest.Header.Del("X-Backend-Storage-Policy-Index")

	if auth != nil {
		GetProxyContext(subRequest).Authorize = auth
	}

	pipeReader, pipeWriter := io.Pipe()
	done := make(chan bool)
	writer := NewPipeResponseWriter(pipeWriter, done, ctx.Logger)
	go func() {
		defer writer.Close()
		ctx.serveHTTPSubrequest(writer, subRequest)
	}()
	<-done

	return pipeReader, writer.Header(), writer.status
}
<|endoftext|>


<commit_before>package router

import (
	wt "github.com/weaveworks/weave/testing"
	"testing"
)

func TestFieldValidator(t *testing.T) {
	testMap := map[string]string{"a": "a"}

	fv := NewFieldValidator(testMap)
	val, err := fv.Value("a")
	wt.AssertNoErr(t, err)
	wt.AssertNoErr(t, fv.Err())
	wt.AssertEqualString(t, val, "a", "a")
	_, err = fv.Value("x")
	wt.AssertFalse(t, err == nil || fv.Err() == nil, "Expected error")
	_, err = fv.Value("a")
	wt.AssertFalse(t, err == nil || fv.Err() == nil, "Previous error should be retained")

	fv = NewFieldValidator(testMap)
	err = fv.CheckEqual("a", "a")
	wt.AssertNoErr(t, err)
	wt.AssertNoErr(t, fv.Err())
	err = fv.CheckEqual("a", "b")
	wt.AssertFalse(t, err == nil || fv.Err() == nil, "Expected error")
	err = fv.CheckEqual("a", "a")
	wt.AssertFalse(t, err == nil || fv.Err() == nil, "Previous error should be retained")
}
<commit_msg>cosmetic<commit_after>package router

import (
	wt "github.com/weaveworks/weave/testing"
	"testing"
)

func TestFieldValidator(t *testing.T) {
	testMap := map[string]string{"a": "a"}

	fv := NewFieldValidator(testMap)
	val, err := fv.Value("a")
	wt.AssertNoErr(t, err)
	wt.AssertNoErr(t, fv.Err())
	wt.AssertEqualString(t, val, "a", "")
	_, err = fv.Value("x")
	wt.AssertFalse(t, err == nil || fv.Err() == nil, "Expected error")
	_, err = fv.Value("a")
	wt.AssertFalse(t, err == nil || fv.Err() == nil, "Previous error should be retained")

	fv = NewFieldValidator(testMap)
	err = fv.CheckEqual("a", "a")
	wt.AssertNoErr(t, err)
	wt.AssertNoErr(t, fv.Err())
	err = fv.CheckEqual("a", "b")
	wt.AssertFalse(t, err == nil || fv.Err() == nil, "Expected error")
	err = fv.CheckEqual("a", "a")
	wt.AssertFalse(t, err == nil || fv.Err() == nil, "Previous error should be retained")
}
<|endoftext|>


<commit_before>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package hipache provides a router implementation that store routes in Redis,
// as specified by Hipache (https://github.com/dotcloud/hipache).
//
// It does not provided any exported type, in order to use the router, you must
// import this package and get the router intance using the function
// router.Get.
//
// In order to use this router, you need to define the "hipache:domain"
// setting.
package hipache

import (
	"errors"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/router"
	"strings"
)

var pool *redis.Pool

var errRouteNotFound = errors.New("Route not found")

func init() {
	router.Register("hipache", hipacheRouter{})
}

func connect() redis.Conn {
	if pool == nil {
		srv, err := config.GetString("hipache:redis-server")
		if err != nil {
			srv = "localhost:6379"
		}
		pool = redis.NewPool(func() (redis.Conn, error) {
			return redis.Dial("tcp", srv)
		}, 10)
	}
	return pool.Get()
}

type hipacheRouter struct{}

func (hipacheRouter) AddBackend(name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"add", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn := connect()
	defer conn.Close()
	_, err = conn.Do("RPUSH", frontend, name)
	if err != nil {
		return &routeError{"add", err}
	}
	return nil
}

func (r hipacheRouter) RemoveBackend(name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"remove", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn := connect()
	defer conn.Close()
	_, err = conn.Do("DEL", frontend)
	if err != nil {
		return &routeError{"remove", err}
	}
	cname, err := r.getCName(name)
	if err != nil {
		return err
	}
	if cname == "" {
		return nil
	}
	_, err = conn.Do("DEL", "frontend:"+cname)
	if err != nil {
		return &routeError{"remove", err}
	}
	_, err = conn.Do("DEL", "cname:"+name)
	if err != nil {
		return &routeError{"remove", err}
	}
	return nil
}

func (r hipacheRouter) AddRoute(name, address string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"add", err}
	}
	frontend := "frontend:" + name + "." + domain
	if err := r.addRoute(frontend, address); err != nil {
		return &routeError{"add", err}
	}
	cname, err := r.getCName(name)
	if err != nil {
		return err
	}
	if cname == "" {
		return nil
	}
	return r.addRoute("frontend:"+cname, address)
}

func (hipacheRouter) addRoute(name, address string) error {
	conn := connect()
	defer conn.Close()
	_, err := conn.Do("RPUSH", name, address)
	if err != nil {
		return &routeError{"add", err}
	}
	return nil
}

func (r hipacheRouter) RemoveRoute(name, address string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"remove", err}
	}
	frontend := "frontend:" + name + "." + domain
	if err := r.removeElement(frontend, address); err != nil {
		return err
	}
	cname, err := r.getCName(name)
	if err != nil {
		return &routeError{"remove", err}
	}
	if cname == "" {
		return nil
	}
	return r.removeElement("frontend:"+cname, address)
}

func (hipacheRouter) getCName(name string) (string, error) {
	conn := connect()
	defer conn.Close()
	cname, err := redis.String(conn.Do("GET", "cname:"+name))
	if err != nil && err != redis.ErrNil {
		return "", &routeError{"getCName", err}
	}
	return cname, nil
}

// validCName returns true if the cname is not a subdomain of
// hipache:domain conf, false otherwise
func (hipacheRouter) validCName(cname string) bool {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return false
	}
	return !strings.Contains(cname, domain)
}

func (r hipacheRouter) SetCName(cname, name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"setCName", err}
	}
	if !r.validCName(cname) {
		err := errors.New(fmt.Sprintf("Invalid CNAME %s. You can't use Tsuru's application domain.", cname))
		return &routeError{"setCName", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn := connect()
	defer conn.Close()
	routes, err := redis.Strings(conn.Do("LRANGE", frontend, 0, -1))
	if err != nil {
		return &routeError{"get", err}
	}
	if oldCName, err := redis.String(conn.Do("GET", "cname:"+name)); err == nil && oldCName != "" {
		err = r.UnsetCName(oldCName, name)
		if err != nil {
			return &routeError{"setCName", err}
		}
	}
	_, err = conn.Do("SET", "cname:"+name, cname)
	if err != nil {
		return &routeError{"set", err}
	}
	frontend = "frontend:" + cname
	for _, r := range routes {
		_, err := conn.Do("RPUSH", frontend, r)
		if err != nil {
			return &routeError{"setCName", err}
		}
	}
	return nil
}

func (r hipacheRouter) UnsetCName(cname, name string) error {
	conn := connect()
	defer conn.Close()
	_, err := conn.Do("DEL", "cname:"+name)
	if err != nil {
		return &routeError{"unsetCName", err}
	}
	_, err = conn.Do("DEL", "frontend:"+cname)
	if err != nil {
		return &routeError{"unsetCName", err}
	}
	return nil
}

func (hipacheRouter) Addr(name string) (string, error) {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return "", &routeError{"get", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn := connect()
	defer conn.Close()
	reply, err := conn.Do("LRANGE", frontend, 0, 0)
	if err != nil {
		return "", &routeError{"get", err}
	}
	backends := reply.([]interface{})
	if len(backends) < 1 {
		return "", errRouteNotFound
	}
	return fmt.Sprintf("%s.%s", name, domain), nil
}

func (hipacheRouter) removeElement(name, address string) error {
	conn := connect()
	defer conn.Close()
	_, err := conn.Do("LREM", name, 0, address)
	if err != nil {
		return &routeError{"remove", err}
	}
	return nil
}

type routeError struct {
	op  string
	err error
}

func (e *routeError) Error() string {
	return fmt.Sprintf("Could not %s route: %s", e.op, e.err)
}
<commit_msg>router/hipache: added more logs in add route.<commit_after>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package hipache provides a router implementation that store routes in Redis,
// as specified by Hipache (https://github.com/dotcloud/hipache).
//
// It does not provided any exported type, in order to use the router, you must
// import this package and get the router intance using the function
// router.Get.
//
// In order to use this router, you need to define the "hipache:domain"
// setting.
package hipache

import (
	"errors"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/globocom/config"
	"github.com/globocom/tsuru/router"
	"strings"
)

var pool *redis.Pool

var errRouteNotFound = errors.New("Route not found")

func init() {
	router.Register("hipache", hipacheRouter{})
}

func connect() redis.Conn {
	if pool == nil {
		srv, err := config.GetString("hipache:redis-server")
		if err != nil {
			srv = "localhost:6379"
		}
		pool = redis.NewPool(func() (redis.Conn, error) {
			return redis.Dial("tcp", srv)
		}, 10)
	}
	return pool.Get()
}

type hipacheRouter struct{}

func (hipacheRouter) AddBackend(name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"add", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn := connect()
	defer conn.Close()
	_, err = conn.Do("RPUSH", frontend, name)
	if err != nil {
		return &routeError{"add", err}
	}
	return nil
}

func (r hipacheRouter) RemoveBackend(name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"remove", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn := connect()
	defer conn.Close()
	_, err = conn.Do("DEL", frontend)
	if err != nil {
		return &routeError{"remove", err}
	}
	cname, err := r.getCName(name)
	if err != nil {
		return err
	}
	if cname == "" {
		return nil
	}
	_, err = conn.Do("DEL", "frontend:"+cname)
	if err != nil {
		return &routeError{"remove", err}
	}
	_, err = conn.Do("DEL", "cname:"+name)
	if err != nil {
		return &routeError{"remove", err}
	}
	return nil
}

func (r hipacheRouter) AddRoute(name, address string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		log.Prinft("error on getting hipache domin in add route for %s - %s", name, address)
		return &routeError{"add", err}
	}
	frontend := "frontend:" + name + "." + domain
	if err := r.addRoute(frontend, address); err != nil {
		log.Prinft("error on add route for %s - %s", name, address)
		return &routeError{"add", err}
	}
	cname, err := r.getCName(name)
	if err != nil {
		log.Prinft("error on get cname in add route for %s - %s", name, address)
		return err
	}
	if cname == "" {
		return nil
	}
	return r.addRoute("frontend:"+cname, address)
}

func (hipacheRouter) addRoute(name, address string) error {
	conn := connect()
	defer conn.Close()
	_, err := conn.Do("RPUSH", name, address)
	if err != nil {
		log.Prinft("error on store in redis in add route for %s - %s", name, address)
		return &routeError{"add", err}
	}
	return nil
}

func (r hipacheRouter) RemoveRoute(name, address string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"remove", err}
	}
	frontend := "frontend:" + name + "." + domain
	if err := r.removeElement(frontend, address); err != nil {
		return err
	}
	cname, err := r.getCName(name)
	if err != nil {
		return &routeError{"remove", err}
	}
	if cname == "" {
		return nil
	}
	return r.removeElement("frontend:"+cname, address)
}

func (hipacheRouter) getCName(name string) (string, error) {
	conn := connect()
	defer conn.Close()
	cname, err := redis.String(conn.Do("GET", "cname:"+name))
	if err != nil && err != redis.ErrNil {
		return "", &routeError{"getCName", err}
	}
	return cname, nil
}

// validCName returns true if the cname is not a subdomain of
// hipache:domain conf, false otherwise
func (hipacheRouter) validCName(cname string) bool {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return false
	}
	return !strings.Contains(cname, domain)
}

func (r hipacheRouter) SetCName(cname, name string) error {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return &routeError{"setCName", err}
	}
	if !r.validCName(cname) {
		err := errors.New(fmt.Sprintf("Invalid CNAME %s. You can't use Tsuru's application domain.", cname))
		return &routeError{"setCName", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn := connect()
	defer conn.Close()
	routes, err := redis.Strings(conn.Do("LRANGE", frontend, 0, -1))
	if err != nil {
		return &routeError{"get", err}
	}
	if oldCName, err := redis.String(conn.Do("GET", "cname:"+name)); err == nil && oldCName != "" {
		err = r.UnsetCName(oldCName, name)
		if err != nil {
			return &routeError{"setCName", err}
		}
	}
	_, err = conn.Do("SET", "cname:"+name, cname)
	if err != nil {
		return &routeError{"set", err}
	}
	frontend = "frontend:" + cname
	for _, r := range routes {
		_, err := conn.Do("RPUSH", frontend, r)
		if err != nil {
			return &routeError{"setCName", err}
		}
	}
	return nil
}

func (r hipacheRouter) UnsetCName(cname, name string) error {
	conn := connect()
	defer conn.Close()
	_, err := conn.Do("DEL", "cname:"+name)
	if err != nil {
		return &routeError{"unsetCName", err}
	}
	_, err = conn.Do("DEL", "frontend:"+cname)
	if err != nil {
		return &routeError{"unsetCName", err}
	}
	return nil
}

func (hipacheRouter) Addr(name string) (string, error) {
	domain, err := config.GetString("hipache:domain")
	if err != nil {
		return "", &routeError{"get", err}
	}
	frontend := "frontend:" + name + "." + domain
	conn := connect()
	defer conn.Close()
	reply, err := conn.Do("LRANGE", frontend, 0, 0)
	if err != nil {
		return "", &routeError{"get", err}
	}
	backends := reply.([]interface{})
	if len(backends) < 1 {
		return "", errRouteNotFound
	}
	return fmt.Sprintf("%s.%s", name, domain), nil
}

func (hipacheRouter) removeElement(name, address string) error {
	conn := connect()
	defer conn.Close()
	_, err := conn.Do("LREM", name, 0, address)
	if err != nil {
		return &routeError{"remove", err}
	}
	return nil
}

type routeError struct {
	op  string
	err error
}

func (e *routeError) Error() string {
	return fmt.Sprintf("Could not %s route: %s", e.op, e.err)
}
<|endoftext|>


<commit_before>// MIT License
//
// Copyright (c) 2018 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/ghodss/yaml"
)

// Bridge respresents the hue bridge in your system.
type Bridge struct {
	IP       string `json:"ip"`
	Username string `json:"username"`
}

// Location represents the geolocation for which sunrise and sunset will be calculated.
type Location struct {
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
}

// WebInterface respresents the webinterface of Kelvin.
type WebInterface struct {
	Enabled bool `json:"enabled"`
	Port    int  `json:"port"`
}

// LightSchedule represents the schedule for any given day for the associated lights.
type LightSchedule struct {
	Name                    string                  `json:"name"`
	AssociatedDeviceIDs     []int                   `json:"associatedDeviceIDs"`
	EnableWhenLightsAppear  bool                    `json:"enableWhenLightsAppear"`
	DefaultColorTemperature int                     `json:"defaultColorTemperature"`
	DefaultBrightness       int                     `json:"defaultBrightness"`
	BeforeSunrise           []TimedColorTemperature `json:"beforeSunrise"`
	AfterSunset             []TimedColorTemperature `json:"afterSunset"`
}

// TimedColorTemperature represents a light configuration which will be
// reached at the given time.
type TimedColorTemperature struct {
	Time             string `json:"time"`
	ColorTemperature int    `json:"colorTemperature"`
	Brightness       int    `json:"brightness"`
}

// Configuration encapsulates all relevant parameters for Kelvin to operate.
type Configuration struct {
	ConfigurationFile string          `json:"-"`
	Hash              string          `json:"-"`
	Version           int             `json:"version"`
	Bridge            Bridge          `json:"bridge"`
	Location          Location        `json:"location"`
	WebInterface      WebInterface    `json:"webinterface"`
	Schedules         []LightSchedule `json:"schedules"`
}

// TimeStamp represents a parsed and validated TimedColorTemperature.
type TimeStamp struct {
	Time             time.Time
	ColorTemperature int
	Brightness       int
}

var latestConfigurationVersion = 0

func (configuration *Configuration) initializeDefaults() {
	configuration.Version = latestConfigurationVersion

	var bedTime TimedColorTemperature
	bedTime.Time = "22:00"
	bedTime.ColorTemperature = 2000
	bedTime.Brightness = 60

	var tvTime TimedColorTemperature
	tvTime.Time = "20:00"
	tvTime.ColorTemperature = 2300
	tvTime.Brightness = 80

	var wakeupTime TimedColorTemperature
	wakeupTime.Time = "4:00"
	wakeupTime.ColorTemperature = 2000
	wakeupTime.Brightness = 60

	var defaultSchedule LightSchedule
	defaultSchedule.Name = "default"
	defaultSchedule.AssociatedDeviceIDs = []int{}
	defaultSchedule.DefaultColorTemperature = 2750
	defaultSchedule.DefaultBrightness = 100
	defaultSchedule.AfterSunset = []TimedColorTemperature{tvTime, bedTime}
	defaultSchedule.BeforeSunrise = []TimedColorTemperature{wakeupTime}

	configuration.Schedules = []LightSchedule{defaultSchedule}

	var webinterface WebInterface
	webinterface.Enabled = false
	webinterface.Port = 8080
	configuration.WebInterface = webinterface
}

// InitializeConfiguration creates and returns an initialized
// configuration.
// If no configuration can be found on disk, one with default values
// will be created.
func InitializeConfiguration(configurationFile string, enableWebInterface bool) (Configuration, error) {
	var configuration Configuration
	configuration.ConfigurationFile = configurationFile
	if configuration.Exists() {
		err := configuration.Read()
		if err != nil {
			return configuration, err
		}
		log.Printf("⚙ Configuration %v loaded", configuration.ConfigurationFile)
	} else {
		// write default config to disk
		configuration.initializeDefaults()
		err := configuration.Write()
		if err != nil {
			return configuration, err
		}
		log.Println("⚙ Default configuration generated")
	}

	// Overwrite interface configuration with startup parameter
	if enableWebInterface {
		configuration.WebInterface.Enabled = true
		err := configuration.Write()
		if err != nil {
			return configuration, err
		}
	}
	return configuration, nil
}

// Write saves a configuration to disk.
func (configuration *Configuration) Write() error {
	if configuration.ConfigurationFile == "" {
		return errors.New("No configuration filename configured")
	}

	if !configuration.HasChanged() {
		log.Debugf("⚙ Configuration hasn't changed. Omitting write.")
		return nil
	}
	log.Debugf("⚙ Configuration changed. Saving to %v", configuration.ConfigurationFile)
	raw, err := json.MarshalIndent(configuration, "", "  ")
	if err != nil {
		return err
	}

	// Convert JSON to YAML if needed
	if isYAMLFile(configuration.ConfigurationFile) {
		raw, err = yaml.JSONToYAML(raw)
		if err != nil {
			return err
		}
	}

	err = ioutil.WriteFile(configuration.ConfigurationFile, raw, 0644)
	if err != nil {
		return err
	}

	configuration.Hash = configuration.HashValue()
	log.Debugf("⚙ Updated configuration hash")
	return nil
}

// Read loads a configuration from disk.
func (configuration *Configuration) Read() error {
	if configuration.ConfigurationFile == "" {
		return errors.New("No configuration filename configured")
	}

	raw, err := ioutil.ReadFile(configuration.ConfigurationFile)
	if err != nil {
		return err
	}

	// Convert YAML to JSON if needed
	if isYAMLFile(configuration.ConfigurationFile) {
		raw, err = yaml.YAMLToJSON(raw)
		if err != nil {
			return err
		}
	}

	err = json.Unmarshal(raw, configuration)
	if err != nil {
		return err
	}

	if len(configuration.Schedules) == 0 {
		log.Warningf("⚙ Your current configuration doesn't contain any schedules! Generating default schedule...")
		err := configuration.backup()
		if err != nil {
			log.Warningf("⚙ Could not create backup: %v", err)
		} else {
			log.Printf("⚙ Configuration backup created.")
			configuration.initializeDefaults()
			log.Printf("⚙ Default schedule created.")
			configuration.Write()
		}
	}
	configuration.Hash = configuration.HashValue()
	log.Debugf("⚙ Updated configuration hash.")

	configuration.migrateToLatestVersion()
	configuration.Write()
	return nil
}

func (configuration *Configuration) lightScheduleForDay(light int, date time.Time) (Schedule, error) {
	// initialize schedule with end of day
	var schedule Schedule
	yr, mth, dy := date.Date()
	schedule.endOfDay = time.Date(yr, mth, dy, 23, 59, 59, 59, date.Location())

	var lightSchedule LightSchedule
	found := false
	for _, candidate := range configuration.Schedules {
		if containsInt(candidate.AssociatedDeviceIDs, light) {
			lightSchedule = candidate
			found = true
			break
		}
	}

	if !found {
		return schedule, fmt.Errorf("Light %d is not associated with any schedule in configuration", light)
	}

	schedule.sunrise = TimeStamp{CalculateSunrise(date, configuration.Location.Latitude, configuration.Location.Longitude), lightSchedule.DefaultColorTemperature, lightSchedule.DefaultBrightness}
	schedule.sunset = TimeStamp{CalculateSunset(date, configuration.Location.Latitude, configuration.Location.Longitude), lightSchedule.DefaultColorTemperature, lightSchedule.DefaultBrightness}

	// Before sunrise candidates
	schedule.beforeSunrise = []TimeStamp{}
	for _, candidate := range lightSchedule.BeforeSunrise {
		timestamp, err := candidate.AsTimestamp(date)
		if err != nil {
			log.Warningf("⚙ Found invalid configuration entry: %+v (Error: %v)", candidate, err)
			continue
		}
		schedule.beforeSunrise = append(schedule.beforeSunrise, timestamp)
	}

	// After sunset candidates
	schedule.afterSunset = []TimeStamp{}
	for _, candidate := range lightSchedule.AfterSunset {
		timestamp, err := candidate.AsTimestamp(date)
		if err != nil {
			log.Warningf("⚙ Found invalid configuration entry: %+v (Error: %v)", candidate, err)
			continue
		}
		schedule.afterSunset = append(schedule.afterSunset, timestamp)
	}

	schedule.enableWhenLightsAppear = lightSchedule.EnableWhenLightsAppear
	return schedule, nil
}

// Exists return true if a configuration file is found on disk.
// False otherwise.
func (configuration *Configuration) Exists() bool {
	if configuration.ConfigurationFile == "" {
		return false
	}

	if _, err := os.Stat(configuration.ConfigurationFile); os.IsNotExist(err) {
		return false
	}
	return true
}

// HasChanged will detect changes to the configuration struct.
func (configuration *Configuration) HasChanged() bool {
	if configuration.Hash == "" {
		return true
	}
	return configuration.HashValue() != configuration.Hash
}

// HashValue will calculate a SHA256 hash of the configuration struct.
func (configuration *Configuration) HashValue() string {
	json, _ := json.Marshal(configuration)
	return fmt.Sprintf("%x", sha256.Sum256(json))
}

// AsTimestamp parses and validates a TimedColorTemperature and returns
// a corresponding TimeStamp.
func (color *TimedColorTemperature) AsTimestamp(referenceTime time.Time) (TimeStamp, error) {
	layout := "15:04"
	t, err := time.Parse(layout, color.Time)
	if err != nil {
		return TimeStamp{time.Now(), color.ColorTemperature, color.Brightness}, err
	}
	yr, mth, day := referenceTime.Date()
	targetTime := time.Date(yr, mth, day, t.Hour(), t.Minute(), t.Second(), 0, referenceTime.Location())

	return TimeStamp{targetTime, color.ColorTemperature, color.Brightness}, nil
}

func (configuration *Configuration) backup() error {
	backupFilename := configuration.ConfigurationFile + "_" + time.Now().Format("01022006")
	log.Debugf("⚙ Moving configuration to %s.", backupFilename)
	return os.Rename(configuration.ConfigurationFile, backupFilename)
}
<commit_msg>Add more details to invalid configuration log<commit_after>// MIT License
//
// Copyright (c) 2018 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/ghodss/yaml"
)

// Bridge respresents the hue bridge in your system.
type Bridge struct {
	IP       string `json:"ip"`
	Username string `json:"username"`
}

// Location represents the geolocation for which sunrise and sunset will be calculated.
type Location struct {
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
}

// WebInterface respresents the webinterface of Kelvin.
type WebInterface struct {
	Enabled bool `json:"enabled"`
	Port    int  `json:"port"`
}

// LightSchedule represents the schedule for any given day for the associated lights.
type LightSchedule struct {
	Name                    string                  `json:"name"`
	AssociatedDeviceIDs     []int                   `json:"associatedDeviceIDs"`
	EnableWhenLightsAppear  bool                    `json:"enableWhenLightsAppear"`
	DefaultColorTemperature int                     `json:"defaultColorTemperature"`
	DefaultBrightness       int                     `json:"defaultBrightness"`
	BeforeSunrise           []TimedColorTemperature `json:"beforeSunrise"`
	AfterSunset             []TimedColorTemperature `json:"afterSunset"`
}

// TimedColorTemperature represents a light configuration which will be
// reached at the given time.
type TimedColorTemperature struct {
	Time             string `json:"time"`
	ColorTemperature int    `json:"colorTemperature"`
	Brightness       int    `json:"brightness"`
}

// Configuration encapsulates all relevant parameters for Kelvin to operate.
type Configuration struct {
	ConfigurationFile string          `json:"-"`
	Hash              string          `json:"-"`
	Version           int             `json:"version"`
	Bridge            Bridge          `json:"bridge"`
	Location          Location        `json:"location"`
	WebInterface      WebInterface    `json:"webinterface"`
	Schedules         []LightSchedule `json:"schedules"`
}

// TimeStamp represents a parsed and validated TimedColorTemperature.
type TimeStamp struct {
	Time             time.Time
	ColorTemperature int
	Brightness       int
}

var latestConfigurationVersion = 0

func (configuration *Configuration) initializeDefaults() {
	configuration.Version = latestConfigurationVersion

	var bedTime TimedColorTemperature
	bedTime.Time = "22:00"
	bedTime.ColorTemperature = 2000
	bedTime.Brightness = 60

	var tvTime TimedColorTemperature
	tvTime.Time = "20:00"
	tvTime.ColorTemperature = 2300
	tvTime.Brightness = 80

	var wakeupTime TimedColorTemperature
	wakeupTime.Time = "4:00"
	wakeupTime.ColorTemperature = 2000
	wakeupTime.Brightness = 60

	var defaultSchedule LightSchedule
	defaultSchedule.Name = "default"
	defaultSchedule.AssociatedDeviceIDs = []int{}
	defaultSchedule.DefaultColorTemperature = 2750
	defaultSchedule.DefaultBrightness = 100
	defaultSchedule.AfterSunset = []TimedColorTemperature{tvTime, bedTime}
	defaultSchedule.BeforeSunrise = []TimedColorTemperature{wakeupTime}

	configuration.Schedules = []LightSchedule{defaultSchedule}

	var webinterface WebInterface
	webinterface.Enabled = false
	webinterface.Port = 8080
	configuration.WebInterface = webinterface
}

// InitializeConfiguration creates and returns an initialized
// configuration.
// If no configuration can be found on disk, one with default values
// will be created.
func InitializeConfiguration(configurationFile string, enableWebInterface bool) (Configuration, error) {
	var configuration Configuration
	configuration.ConfigurationFile = configurationFile
	if configuration.Exists() {
		err := configuration.Read()
		if err != nil {
			return configuration, err
		}
		log.Printf("⚙ Configuration %v loaded", configuration.ConfigurationFile)
	} else {
		// write default config to disk
		configuration.initializeDefaults()
		err := configuration.Write()
		if err != nil {
			return configuration, err
		}
		log.Println("⚙ Default configuration generated")
	}

	// Overwrite interface configuration with startup parameter
	if enableWebInterface {
		configuration.WebInterface.Enabled = true
		err := configuration.Write()
		if err != nil {
			return configuration, err
		}
	}
	return configuration, nil
}

// Write saves a configuration to disk.
func (configuration *Configuration) Write() error {
	if configuration.ConfigurationFile == "" {
		return errors.New("No configuration filename configured")
	}

	if !configuration.HasChanged() {
		log.Debugf("⚙ Configuration hasn't changed. Omitting write.")
		return nil
	}
	log.Debugf("⚙ Configuration changed. Saving to %v", configuration.ConfigurationFile)
	raw, err := json.MarshalIndent(configuration, "", "  ")
	if err != nil {
		return err
	}

	// Convert JSON to YAML if needed
	if isYAMLFile(configuration.ConfigurationFile) {
		raw, err = yaml.JSONToYAML(raw)
		if err != nil {
			return err
		}
	}

	err = ioutil.WriteFile(configuration.ConfigurationFile, raw, 0644)
	if err != nil {
		return err
	}

	configuration.Hash = configuration.HashValue()
	log.Debugf("⚙ Updated configuration hash")
	return nil
}

// Read loads a configuration from disk.
func (configuration *Configuration) Read() error {
	if configuration.ConfigurationFile == "" {
		return errors.New("No configuration filename configured")
	}

	raw, err := ioutil.ReadFile(configuration.ConfigurationFile)
	if err != nil {
		return err
	}

	// Convert YAML to JSON if needed
	if isYAMLFile(configuration.ConfigurationFile) {
		raw, err = yaml.YAMLToJSON(raw)
		if err != nil {
			return err
		}
	}

	err = json.Unmarshal(raw, configuration)
	if err != nil {
		return err
	}

	if len(configuration.Schedules) == 0 {
		log.Warningf("⚙ Your current configuration doesn't contain any schedules! Generating default schedule...")
		err := configuration.backup()
		if err != nil {
			log.Warningf("⚙ Could not create backup: %v", err)
		} else {
			log.Printf("⚙ Configuration backup created.")
			configuration.initializeDefaults()
			log.Printf("⚙ Default schedule created.")
			configuration.Write()
		}
	}
	configuration.Hash = configuration.HashValue()
	log.Debugf("⚙ Updated configuration hash.")

	configuration.migrateToLatestVersion()
	configuration.Write()
	return nil
}

func (configuration *Configuration) lightScheduleForDay(light int, date time.Time) (Schedule, error) {
	// initialize schedule with end of day
	var schedule Schedule
	yr, mth, dy := date.Date()
	schedule.endOfDay = time.Date(yr, mth, dy, 23, 59, 59, 59, date.Location())

	var lightSchedule LightSchedule
	found := false
	for _, candidate := range configuration.Schedules {
		if containsInt(candidate.AssociatedDeviceIDs, light) {
			lightSchedule = candidate
			found = true
			break
		}
	}

	if !found {
		return schedule, fmt.Errorf("Light %d is not associated with any schedule in configuration", light)
	}

	schedule.sunrise = TimeStamp{CalculateSunrise(date, configuration.Location.Latitude, configuration.Location.Longitude), lightSchedule.DefaultColorTemperature, lightSchedule.DefaultBrightness}
	schedule.sunset = TimeStamp{CalculateSunset(date, configuration.Location.Latitude, configuration.Location.Longitude), lightSchedule.DefaultColorTemperature, lightSchedule.DefaultBrightness}

	// Before sunrise candidates
	schedule.beforeSunrise = []TimeStamp{}
	for _, candidate := range lightSchedule.BeforeSunrise {
		timestamp, err := candidate.AsTimestamp(date)
		if err != nil {
			log.Warningf("⚙ Found invalid configuration entry before sunrise: %+v (Error: %v)", candidate, err)
			continue
		}
		schedule.beforeSunrise = append(schedule.beforeSunrise, timestamp)
	}

	// After sunset candidates
	schedule.afterSunset = []TimeStamp{}
	for _, candidate := range lightSchedule.AfterSunset {
		timestamp, err := candidate.AsTimestamp(date)
		if err != nil {
			log.Warningf("⚙ Found invalid configuration entry after sunset: %+v (Error: %v)", candidate, err)
			continue
		}
		schedule.afterSunset = append(schedule.afterSunset, timestamp)
	}

	schedule.enableWhenLightsAppear = lightSchedule.EnableWhenLightsAppear
	return schedule, nil
}

// Exists return true if a configuration file is found on disk.
// False otherwise.
func (configuration *Configuration) Exists() bool {
	if configuration.ConfigurationFile == "" {
		return false
	}

	if _, err := os.Stat(configuration.ConfigurationFile); os.IsNotExist(err) {
		return false
	}
	return true
}

// HasChanged will detect changes to the configuration struct.
func (configuration *Configuration) HasChanged() bool {
	if configuration.Hash == "" {
		return true
	}
	return configuration.HashValue() != configuration.Hash
}

// HashValue will calculate a SHA256 hash of the configuration struct.
func (configuration *Configuration) HashValue() string {
	json, _ := json.Marshal(configuration)
	return fmt.Sprintf("%x", sha256.Sum256(json))
}

// AsTimestamp parses and validates a TimedColorTemperature and returns
// a corresponding TimeStamp.
func (color *TimedColorTemperature) AsTimestamp(referenceTime time.Time) (TimeStamp, error) {
	layout := "15:04"
	t, err := time.Parse(layout, color.Time)
	if err != nil {
		return TimeStamp{time.Now(), color.ColorTemperature, color.Brightness}, err
	}
	yr, mth, day := referenceTime.Date()
	targetTime := time.Date(yr, mth, day, t.Hour(), t.Minute(), t.Second(), 0, referenceTime.Location())

	return TimeStamp{targetTime, color.ColorTemperature, color.Brightness}, nil
}

func (configuration *Configuration) backup() error {
	backupFilename := configuration.ConfigurationFile + "_" + time.Now().Format("01022006")
	log.Debugf("⚙ Moving configuration to %s.", backupFilename)
	return os.Rename(configuration.ConfigurationFile, backupFilename)
}
<|endoftext|>


<commit_before>package consensus

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"hash/crc32"
	"io"
	"path/filepath"
	"time"

	"github.com/pkg/errors"

	wire "github.com/tendermint/go-wire"
	"github.com/tendermint/tendermint/types"
	auto "github.com/tendermint/tmlibs/autofile"
	cmn "github.com/tendermint/tmlibs/common"
)

const (
	maxMsgSizeBytes = 10024 // 10MB
)

//--------------------------------------------------------
// types and functions for savings consensus messages

type TimedWALMessage struct {
	Time time.Time  `json:"time"` // for debugging purposes
	Msg  WALMessage `json:"msg"`
}

// EndHeightMessage marks the end of the given height inside WAL.
// @internal used by scripts/wal2json util.
type EndHeightMessage struct {
	Height int64 `json:"height"`
}

type WALMessage interface{}

var _ = wire.RegisterInterface(
	struct{ WALMessage }{},
	wire.ConcreteType{types.EventDataRoundState{}, 0x01},
	wire.ConcreteType{msgInfo{}, 0x02},
	wire.ConcreteType{timeoutInfo{}, 0x03},
	wire.ConcreteType{EndHeightMessage{}, 0x04},
)

//--------------------------------------------------------
// Simple write-ahead logger

// WAL is an interface for any write-ahead logger.
type WAL interface {
	Save(WALMessage)
	Group() *auto.Group
	SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error)

	Start() error
	Stop() error
	Wait()
}

// Write ahead logger writes msgs to disk before they are processed.
// Can be used for crash-recovery and deterministic replay
// TODO: currently the wal is overwritten during replay catchup
//   give it a mode so it's either reading or appending - must read to end to start appending again
type baseWAL struct {
	cmn.BaseService

	group *auto.Group
	light bool // ignore block parts

	enc *WALEncoder
}

func NewWAL(walFile string, light bool) (*baseWAL, error) {
	err := cmn.EnsureDir(filepath.Dir(walFile), 0700)
	if err != nil {
		return nil, errors.Wrap(err, "failed to ensure WAL directory is in place")
	}

	group, err := auto.OpenGroup(walFile)
	if err != nil {
		return nil, err
	}
	wal := &baseWAL{
		group: group,
		light: light,
		enc:   NewWALEncoder(group),
	}
	wal.BaseService = *cmn.NewBaseService(nil, "baseWAL", wal)
	return wal, nil
}

func (wal *baseWAL) Group() *auto.Group {
	return wal.group
}

func (wal *baseWAL) OnStart() error {
	size, err := wal.group.Head.Size()
	if err != nil {
		return err
	} else if size == 0 {
		wal.Save(EndHeightMessage{0})
	}
	err = wal.group.Start()
	return err
}

func (wal *baseWAL) OnStop() {
	wal.BaseService.OnStop()
	wal.group.Stop()
}

// called in newStep and for each pass in receiveRoutine
func (wal *baseWAL) Save(msg WALMessage) {
	if wal == nil {
		return
	}

	if wal.light {
		// in light mode we only write new steps, timeouts, and our own votes (no proposals, block parts)
		if mi, ok := msg.(msgInfo); ok {
			if mi.PeerKey != "" {
				return
			}
		}
	}

	// Write the wal message
	if err := wal.enc.Encode(&TimedWALMessage{time.Now(), msg}); err != nil {
		cmn.PanicQ(cmn.Fmt("Error writing msg to consensus wal: %v \n\nMessage: %v", err, msg))
	}

	// TODO: only flush when necessary
	if err := wal.group.Flush(); err != nil {
		cmn.PanicQ(cmn.Fmt("Error flushing consensus wal buf to file. Error: %v \n", err))
	}
}

// WALSearchOptions are optional arguments to SearchForEndHeight.
type WALSearchOptions struct {
	IgnoreDataCorruptionErrors bool
}

// SearchForEndHeight searches for the EndHeightMessage with the height and
// returns an auto.GroupReader, whenever it was found or not and an error.
// Group reader will be nil if found equals false.
//
// CONTRACT: caller must close group reader.
func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
	var msg *TimedWALMessage

	// NOTE: starting from the last file in the group because we're usually
	// searching for the last height. See replay.go
	min, max := wal.group.MinIndex(), wal.group.MaxIndex()
	wal.Logger.Debug("Searching for height", "height", height, "min", min, "max", max)
	for index := max; index >= min; index-- {
		gr, err = wal.group.NewReader(index)
		if err != nil {
			return nil, false, err
		}

		dec := NewWALDecoder(gr)
		for {
			msg, err = dec.Decode()
			if err == io.EOF {
				// check next file
				break
			}
			if options.IgnoreDataCorruptionErrors && IsDataCorruptionError(err) {
				// do nothing
			} else if err != nil {
				gr.Close()
				return nil, false, err
			}

			if m, ok := msg.Msg.(EndHeightMessage); ok {
				if m.Height == height { // found
					wal.Logger.Debug("Found", "height", height, "index", index)
					return gr, true, nil
				}
			}
		}
		gr.Close()
	}

	return nil, false, nil
}

///////////////////////////////////////////////////////////////////////////////

// A WALEncoder writes custom-encoded WAL messages to an output stream.
//
// Format: 4 bytes CRC sum + 4 bytes length + arbitrary-length value (go-wire encoded)
type WALEncoder struct {
	wr io.Writer
}

// NewWALEncoder returns a new encoder that writes to wr.
func NewWALEncoder(wr io.Writer) *WALEncoder {
	return &WALEncoder{wr}
}

// Encode writes the custom encoding of v to the stream.
func (enc *WALEncoder) Encode(v *TimedWALMessage) error {
	data := wire.BinaryBytes(v)

	crc := crc32.Checksum(data, crc32c)
	length := uint32(len(data))
	totalLength := 8 + int(length)

	msg := make([]byte, totalLength)
	binary.BigEndian.PutUint32(msg[0:4], crc)
	binary.BigEndian.PutUint32(msg[4:8], length)
	copy(msg[8:], data)

	_, err := enc.wr.Write(msg)

	return err
}

///////////////////////////////////////////////////////////////////////////////

// IsDataCorruptionError returns true if data has been corrupted inside WAL.
func IsDataCorruptionError(err error) bool {
	_, ok := err.(DataCorruptionError)
	return ok
}

type DataCorruptionError struct {
	cause error
}

func (e DataCorruptionError) Error() string {
	return fmt.Sprintf("DataCorruptionError[%v]", e.cause)
}

func (e DataCorruptionError) Cause() error {
	return e.cause
}

// A WALDecoder reads and decodes custom-encoded WAL messages from an input
// stream. See WALEncoder for the format used.
//
// It will also compare the checksums and make sure data size is equal to the
// length from the header. If that is not the case, error will be returned.
type WALDecoder struct {
	rd io.Reader
}

// NewWALDecoder returns a new decoder that reads from rd.
func NewWALDecoder(rd io.Reader) *WALDecoder {
	return &WALDecoder{rd}
}

// Decode reads the next custom-encoded value from its reader and returns it.
func (dec *WALDecoder) Decode() (*TimedWALMessage, error) {
	b := make([]byte, 4)

	n, err := dec.rd.Read(b)
	if err == io.EOF {
		return nil, err
	}
	if err != nil {
		return nil, fmt.Errorf("failed to read checksum: %v", err)
	}
	crc := binary.BigEndian.Uint32(b)

	b = make([]byte, 4)
	n, err = dec.rd.Read(b)
	if err == io.EOF {
		return nil, err
	}
	if err != nil {
		return nil, fmt.Errorf("failed to read length: %v", err)
	}
	length := binary.BigEndian.Uint32(b)

	if length > maxMsgSizeBytes {
		return nil, DataCorruptionError{fmt.Errorf("length %d exceeded maximum possible value %d", length, maxMsgSizeBytes)}
	}

	data := make([]byte, length)
	n, err = dec.rd.Read(data)
	if err == io.EOF {
		return nil, err
	}
	if err != nil {
		return nil, fmt.Errorf("not enough bytes for data: %v (want: %d, read: %v)", err, length, n)
	}

	// check checksum before decoding data
	actualCRC := crc32.Checksum(data, crc32c)
	if actualCRC != crc {
		return nil, DataCorruptionError{fmt.Errorf("checksums do not match: (read: %v, actual: %v)", crc, actualCRC)}
	}

	var nn int
	var res *TimedWALMessage // nolint: gosimple
	res = wire.ReadBinary(&TimedWALMessage{}, bytes.NewBuffer(data), int(length), &nn, &err).(*TimedWALMessage)
	if err != nil {
		return nil, DataCorruptionError{fmt.Errorf("failed to decode data: %v", err)}
	}

	return res, err
}

type nilWAL struct{}

func (nilWAL) Save(m WALMessage)  {}
func (nilWAL) Group() *auto.Group { return nil }
func (nilWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
	return nil, false, nil
}
func (nilWAL) Start() error { return nil }
func (nilWAL) Stop() error  { return nil }
func (nilWAL) Wait()        {}
<commit_msg>fix error msg<commit_after>package consensus

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"hash/crc32"
	"io"
	"path/filepath"
	"time"

	"github.com/pkg/errors"

	wire "github.com/tendermint/go-wire"
	"github.com/tendermint/tendermint/types"
	auto "github.com/tendermint/tmlibs/autofile"
	cmn "github.com/tendermint/tmlibs/common"
)

const (
	maxMsgSizeBytes = 10024 // 10MB
)

//--------------------------------------------------------
// types and functions for savings consensus messages

type TimedWALMessage struct {
	Time time.Time  `json:"time"` // for debugging purposes
	Msg  WALMessage `json:"msg"`
}

// EndHeightMessage marks the end of the given height inside WAL.
// @internal used by scripts/wal2json util.
type EndHeightMessage struct {
	Height int64 `json:"height"`
}

type WALMessage interface{}

var _ = wire.RegisterInterface(
	struct{ WALMessage }{},
	wire.ConcreteType{types.EventDataRoundState{}, 0x01},
	wire.ConcreteType{msgInfo{}, 0x02},
	wire.ConcreteType{timeoutInfo{}, 0x03},
	wire.ConcreteType{EndHeightMessage{}, 0x04},
)

//--------------------------------------------------------
// Simple write-ahead logger

// WAL is an interface for any write-ahead logger.
type WAL interface {
	Save(WALMessage)
	Group() *auto.Group
	SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error)

	Start() error
	Stop() error
	Wait()
}

// Write ahead logger writes msgs to disk before they are processed.
// Can be used for crash-recovery and deterministic replay
// TODO: currently the wal is overwritten during replay catchup
//   give it a mode so it's either reading or appending - must read to end to start appending again
type baseWAL struct {
	cmn.BaseService

	group *auto.Group
	light bool // ignore block parts

	enc *WALEncoder
}

func NewWAL(walFile string, light bool) (*baseWAL, error) {
	err := cmn.EnsureDir(filepath.Dir(walFile), 0700)
	if err != nil {
		return nil, errors.Wrap(err, "failed to ensure WAL directory is in place")
	}

	group, err := auto.OpenGroup(walFile)
	if err != nil {
		return nil, err
	}
	wal := &baseWAL{
		group: group,
		light: light,
		enc:   NewWALEncoder(group),
	}
	wal.BaseService = *cmn.NewBaseService(nil, "baseWAL", wal)
	return wal, nil
}

func (wal *baseWAL) Group() *auto.Group {
	return wal.group
}

func (wal *baseWAL) OnStart() error {
	size, err := wal.group.Head.Size()
	if err != nil {
		return err
	} else if size == 0 {
		wal.Save(EndHeightMessage{0})
	}
	err = wal.group.Start()
	return err
}

func (wal *baseWAL) OnStop() {
	wal.BaseService.OnStop()
	wal.group.Stop()
}

// called in newStep and for each pass in receiveRoutine
func (wal *baseWAL) Save(msg WALMessage) {
	if wal == nil {
		return
	}

	if wal.light {
		// in light mode we only write new steps, timeouts, and our own votes (no proposals, block parts)
		if mi, ok := msg.(msgInfo); ok {
			if mi.PeerKey != "" {
				return
			}
		}
	}

	// Write the wal message
	if err := wal.enc.Encode(&TimedWALMessage{time.Now(), msg}); err != nil {
		cmn.PanicQ(cmn.Fmt("Error writing msg to consensus wal: %v \n\nMessage: %v", err, msg))
	}

	// TODO: only flush when necessary
	if err := wal.group.Flush(); err != nil {
		cmn.PanicQ(cmn.Fmt("Error flushing consensus wal buf to file. Error: %v \n", err))
	}
}

// WALSearchOptions are optional arguments to SearchForEndHeight.
type WALSearchOptions struct {
	IgnoreDataCorruptionErrors bool
}

// SearchForEndHeight searches for the EndHeightMessage with the height and
// returns an auto.GroupReader, whenever it was found or not and an error.
// Group reader will be nil if found equals false.
//
// CONTRACT: caller must close group reader.
func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
	var msg *TimedWALMessage

	// NOTE: starting from the last file in the group because we're usually
	// searching for the last height. See replay.go
	min, max := wal.group.MinIndex(), wal.group.MaxIndex()
	wal.Logger.Debug("Searching for height", "height", height, "min", min, "max", max)
	for index := max; index >= min; index-- {
		gr, err = wal.group.NewReader(index)
		if err != nil {
			return nil, false, err
		}

		dec := NewWALDecoder(gr)
		for {
			msg, err = dec.Decode()
			if err == io.EOF {
				// check next file
				break
			}
			if options.IgnoreDataCorruptionErrors && IsDataCorruptionError(err) {
				// do nothing
			} else if err != nil {
				gr.Close()
				return nil, false, err
			}

			if m, ok := msg.Msg.(EndHeightMessage); ok {
				if m.Height == height { // found
					wal.Logger.Debug("Found", "height", height, "index", index)
					return gr, true, nil
				}
			}
		}
		gr.Close()
	}

	return nil, false, nil
}

///////////////////////////////////////////////////////////////////////////////

// A WALEncoder writes custom-encoded WAL messages to an output stream.
//
// Format: 4 bytes CRC sum + 4 bytes length + arbitrary-length value (go-wire encoded)
type WALEncoder struct {
	wr io.Writer
}

// NewWALEncoder returns a new encoder that writes to wr.
func NewWALEncoder(wr io.Writer) *WALEncoder {
	return &WALEncoder{wr}
}

// Encode writes the custom encoding of v to the stream.
func (enc *WALEncoder) Encode(v *TimedWALMessage) error {
	data := wire.BinaryBytes(v)

	crc := crc32.Checksum(data, crc32c)
	length := uint32(len(data))
	totalLength := 8 + int(length)

	msg := make([]byte, totalLength)
	binary.BigEndian.PutUint32(msg[0:4], crc)
	binary.BigEndian.PutUint32(msg[4:8], length)
	copy(msg[8:], data)

	_, err := enc.wr.Write(msg)

	return err
}

///////////////////////////////////////////////////////////////////////////////

// IsDataCorruptionError returns true if data has been corrupted inside WAL.
func IsDataCorruptionError(err error) bool {
	_, ok := err.(DataCorruptionError)
	return ok
}

type DataCorruptionError struct {
	cause error
}

func (e DataCorruptionError) Error() string {
	return fmt.Sprintf("DataCorruptionError[%v]", e.cause)
}

func (e DataCorruptionError) Cause() error {
	return e.cause
}

// A WALDecoder reads and decodes custom-encoded WAL messages from an input
// stream. See WALEncoder for the format used.
//
// It will also compare the checksums and make sure data size is equal to the
// length from the header. If that is not the case, error will be returned.
type WALDecoder struct {
	rd io.Reader
}

// NewWALDecoder returns a new decoder that reads from rd.
func NewWALDecoder(rd io.Reader) *WALDecoder {
	return &WALDecoder{rd}
}

// Decode reads the next custom-encoded value from its reader and returns it.
func (dec *WALDecoder) Decode() (*TimedWALMessage, error) {
	b := make([]byte, 4)

	_, err := dec.rd.Read(b)
	if err == io.EOF {
		return nil, err
	}
	if err != nil {
		return nil, fmt.Errorf("failed to read checksum: %v", err)
	}
	crc := binary.BigEndian.Uint32(b)

	b = make([]byte, 4)
	_, err = dec.rd.Read(b)
	if err == io.EOF {
		return nil, err
	}
	if err != nil {
		return nil, fmt.Errorf("failed to read length: %v", err)
	}
	length := binary.BigEndian.Uint32(b)

	if length > maxMsgSizeBytes {
		return nil, DataCorruptionError{fmt.Errorf("length %d exceeded maximum possible value %d", length, maxMsgSizeBytes)}
	}

	data := make([]byte, length)
	_, err = dec.rd.Read(data)
	if err == io.EOF {
		return nil, err
	}
	if err != nil {
		return nil, fmt.Errorf("failed to read data: %v", err)
	}

	// check checksum before decoding data
	actualCRC := crc32.Checksum(data, crc32c)
	if actualCRC != crc {
		return nil, DataCorruptionError{fmt.Errorf("checksums do not match: (read: %v, actual: %v)", crc, actualCRC)}
	}

	var nn int
	var res *TimedWALMessage // nolint: gosimple
	res = wire.ReadBinary(&TimedWALMessage{}, bytes.NewBuffer(data), int(length), &nn, &err).(*TimedWALMessage)
	if err != nil {
		return nil, DataCorruptionError{fmt.Errorf("failed to decode data: %v", err)}
	}

	return res, err
}

type nilWAL struct{}

func (nilWAL) Save(m WALMessage)  {}
func (nilWAL) Group() *auto.Group { return nil }
func (nilWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (gr *auto.GroupReader, found bool, err error) {
	return nil, false, nil
}
func (nilWAL) Start() error { return nil }
func (nilWAL) Stop() error  { return nil }
func (nilWAL) Wait()        {}
<|endoftext|>