<commit_before>//
// Copyright 2021, Sander van Harmelen
//
// 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 gitlab

import (
	"fmt"
	"net/http"
	"time"
)

// PipelineSchedulesService handles communication with the pipeline
// schedules related methods of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipeline_schedules.html
type PipelineSchedulesService struct {
	client *Client
}

// PipelineSchedule represents a pipeline schedule.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html
type PipelineSchedule struct {
	ID           int        `json:"id"`
	Description  string     `json:"description"`
	Ref          string     `json:"ref"`
	Cron         string     `json:"cron"`
	CronTimezone string     `json:"cron_timezone"`
	NextRunAt    *time.Time `json:"next_run_at"`
	Active       bool       `json:"active"`
	CreatedAt    *time.Time `json:"created_at"`
	UpdatedAt    *time.Time `json:"updated_at"`
	Owner        *User      `json:"owner"`
	LastPipeline struct {
		ID     int    `json:"id"`
		SHA    string `json:"sha"`
		Ref    string `json:"ref"`
		Status string `json:"status"`
	} `json:"last_pipeline"`
	Variables []*PipelineVariable `json:"variables"`
}

// ListPipelineSchedulesOptions represents the available ListPipelineTriggers() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_triggers.html#list-project-triggers
type ListPipelineSchedulesOptions ListOptions

// ListPipelineSchedules gets a list of project triggers.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html
func (s *PipelineSchedulesService) ListPipelineSchedules(pid interface{}, opt *ListPipelineSchedulesOptions, options ...RequestOptionFunc) ([]*PipelineSchedule, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules", PathEscape(project))

	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var ps []*PipelineSchedule
	resp, err := s.client.Do(req, &ps)
	if err != nil {
		return nil, resp, err
	}

	return ps, resp, err
}

// GetPipelineSchedule gets a pipeline schedule.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html
func (s *PipelineSchedulesService) GetPipelineSchedule(pid interface{}, schedule int, options ...RequestOptionFunc) (*PipelineSchedule, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineSchedule)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// CreatePipelineScheduleOptions represents the available
// CreatePipelineSchedule() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
type CreatePipelineScheduleOptions struct {
	Description  *string `url:"description" json:"description"`
	Ref          *string `url:"ref" json:"ref"`
	Cron         *string `url:"cron" json:"cron"`
	CronTimezone *string `url:"cron_timezone,omitempty" json:"cron_timezone,omitempty"`
	Active       *bool   `url:"active,omitempty" json:"active,omitempty"`
}

// CreatePipelineSchedule creates a pipeline schedule.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
func (s *PipelineSchedulesService) CreatePipelineSchedule(pid interface{}, opt *CreatePipelineScheduleOptions, options ...RequestOptionFunc) (*PipelineSchedule, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules", PathEscape(project))

	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineSchedule)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// EditPipelineScheduleOptions represents the available
// EditPipelineSchedule() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
type EditPipelineScheduleOptions struct {
	Description  *string `url:"description,omitempty" json:"description,omitempty"`
	Ref          *string `url:"ref,omitempty" json:"ref,omitempty"`
	Cron         *string `url:"cron,omitempty" json:"cron,omitempty"`
	CronTimezone *string `url:"cron_timezone,omitempty" json:"cron_timezone,omitempty"`
	Active       *bool   `url:"active,omitempty" json:"active,omitempty"`
}

// EditPipelineSchedule edits a pipeline schedule.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule
func (s *PipelineSchedulesService) EditPipelineSchedule(pid interface{}, schedule int, opt *EditPipelineScheduleOptions, options ...RequestOptionFunc) (*PipelineSchedule, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineSchedule)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// TakeOwnershipOfPipelineSchedule sets the owner of the specified
// pipeline schedule to the user issuing the request.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#take-ownership-of-a-pipeline-schedule
func (s *PipelineSchedulesService) TakeOwnershipOfPipelineSchedule(pid interface{}, schedule int, options ...RequestOptionFunc) (*PipelineSchedule, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/take_ownership", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineSchedule)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// DeletePipelineSchedule deletes a pipeline schedule.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#delete-a-pipeline-schedule
func (s *PipelineSchedulesService) DeletePipelineSchedule(pid interface{}, schedule int, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// RunPipelineSchedule triggers a new scheduled pipeline to run immediately.
//
// Gitlab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#run-a-scheduled-pipeline-immediately
func (s *PipelineSchedulesService) RunPipelineSchedule(pid interface{}, schedule int, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/play", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// CreatePipelineScheduleVariableOptions represents the available
// CreatePipelineScheduleVariable() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
type CreatePipelineScheduleVariableOptions struct {
	Key          *string `url:"key" json:"key"`
	Value        *string `url:"value" json:"value"`
	VariableType *string `url:"variable_type,omitempty" json:"variable_type,omitempty"`
}

// CreatePipelineScheduleVariable creates a pipeline schedule variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
func (s *PipelineSchedulesService) CreatePipelineScheduleVariable(pid interface{}, schedule int, opt *CreatePipelineScheduleVariableOptions, options ...RequestOptionFunc) (*PipelineVariable, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineVariable)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// EditPipelineScheduleVariableOptions represents the available
// EditPipelineScheduleVariable() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule-variable
type EditPipelineScheduleVariableOptions struct {
	Value        *string `url:"value" json:"value"`
	VariableType *string `url:"variable_type,omitempty" json:"variable_type,omitempty"`
}

// EditPipelineScheduleVariable creates a pipeline schedule variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule-variable
func (s *PipelineSchedulesService) EditPipelineScheduleVariable(pid interface{}, schedule int, key string, opt *EditPipelineScheduleVariableOptions, options ...RequestOptionFunc) (*PipelineVariable, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables/%s", PathEscape(project), schedule, key)

	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineVariable)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// DeletePipelineScheduleVariable creates a pipeline schedule variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#delete-a-pipeline-schedule-variable
func (s *PipelineSchedulesService) DeletePipelineScheduleVariable(pid interface{}, schedule int, key string, options ...RequestOptionFunc) (*PipelineVariable, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables/%s", PathEscape(project), schedule, key)

	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineVariable)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}
<commit_msg>Implement API to get pipelines triggered by a schedule<commit_after>//
// Copyright 2021, Sander van Harmelen
//
// 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 gitlab

import (
	"fmt"
	"net/http"
	"time"
)

// PipelineSchedulesService handles communication with the pipeline
// schedules related methods of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipeline_schedules.html
type PipelineSchedulesService struct {
	client *Client
}

// PipelineSchedule represents a pipeline schedule.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html
type PipelineSchedule struct {
	ID           int        `json:"id"`
	Description  string     `json:"description"`
	Ref          string     `json:"ref"`
	Cron         string     `json:"cron"`
	CronTimezone string     `json:"cron_timezone"`
	NextRunAt    *time.Time `json:"next_run_at"`
	Active       bool       `json:"active"`
	CreatedAt    *time.Time `json:"created_at"`
	UpdatedAt    *time.Time `json:"updated_at"`
	Owner        *User      `json:"owner"`
	LastPipeline struct {
		ID     int    `json:"id"`
		SHA    string `json:"sha"`
		Ref    string `json:"ref"`
		Status string `json:"status"`
	} `json:"last_pipeline"`
	Variables []*PipelineVariable `json:"variables"`
}

// ListPipelineSchedulesOptions represents the available ListPipelineTriggers() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_triggers.html#list-project-triggers
type ListPipelineSchedulesOptions ListOptions

// ListPipelineSchedules gets a list of project triggers.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html
func (s *PipelineSchedulesService) ListPipelineSchedules(pid interface{}, opt *ListPipelineSchedulesOptions, options ...RequestOptionFunc) ([]*PipelineSchedule, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules", PathEscape(project))

	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var ps []*PipelineSchedule
	resp, err := s.client.Do(req, &ps)
	if err != nil {
		return nil, resp, err
	}

	return ps, resp, err
}

// GetPipelineSchedule gets a pipeline schedule.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html
func (s *PipelineSchedulesService) GetPipelineSchedule(pid interface{}, schedule int, options ...RequestOptionFunc) (*PipelineSchedule, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineSchedule)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// GetPipelinesTriggeredBySchedule gets all pipelines triggered by a pipeline schedule
//
// GitLab API docs:
// https://docs.gitlab.com/ee/api/pipeline_schedules.html#get-all-pipelines-triggered-by-a-pipeline-schedule
func (s *PipelineSchedulesService) GetPipelinesTriggeredBySchedule(pid interface{}, schedule int, options ...RequestOptionFunc) ([]*Pipeline, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/pipelines", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var p []*Pipeline
	resp, err := s.client.Do(req, &p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// CreatePipelineScheduleOptions represents the available
// CreatePipelineSchedule() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
type CreatePipelineScheduleOptions struct {
	Description  *string `url:"description" json:"description"`
	Ref          *string `url:"ref" json:"ref"`
	Cron         *string `url:"cron" json:"cron"`
	CronTimezone *string `url:"cron_timezone,omitempty" json:"cron_timezone,omitempty"`
	Active       *bool   `url:"active,omitempty" json:"active,omitempty"`
}

// CreatePipelineSchedule creates a pipeline schedule.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
func (s *PipelineSchedulesService) CreatePipelineSchedule(pid interface{}, opt *CreatePipelineScheduleOptions, options ...RequestOptionFunc) (*PipelineSchedule, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules", PathEscape(project))

	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineSchedule)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// EditPipelineScheduleOptions represents the available
// EditPipelineSchedule() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
type EditPipelineScheduleOptions struct {
	Description  *string `url:"description,omitempty" json:"description,omitempty"`
	Ref          *string `url:"ref,omitempty" json:"ref,omitempty"`
	Cron         *string `url:"cron,omitempty" json:"cron,omitempty"`
	CronTimezone *string `url:"cron_timezone,omitempty" json:"cron_timezone,omitempty"`
	Active       *bool   `url:"active,omitempty" json:"active,omitempty"`
}

// EditPipelineSchedule edits a pipeline schedule.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule
func (s *PipelineSchedulesService) EditPipelineSchedule(pid interface{}, schedule int, opt *EditPipelineScheduleOptions, options ...RequestOptionFunc) (*PipelineSchedule, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineSchedule)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// TakeOwnershipOfPipelineSchedule sets the owner of the specified
// pipeline schedule to the user issuing the request.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#take-ownership-of-a-pipeline-schedule
func (s *PipelineSchedulesService) TakeOwnershipOfPipelineSchedule(pid interface{}, schedule int, options ...RequestOptionFunc) (*PipelineSchedule, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/take_ownership", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineSchedule)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// DeletePipelineSchedule deletes a pipeline schedule.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#delete-a-pipeline-schedule
func (s *PipelineSchedulesService) DeletePipelineSchedule(pid interface{}, schedule int, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// RunPipelineSchedule triggers a new scheduled pipeline to run immediately.
//
// Gitlab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#run-a-scheduled-pipeline-immediately
func (s *PipelineSchedulesService) RunPipelineSchedule(pid interface{}, schedule int, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/play", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// CreatePipelineScheduleVariableOptions represents the available
// CreatePipelineScheduleVariable() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
type CreatePipelineScheduleVariableOptions struct {
	Key          *string `url:"key" json:"key"`
	Value        *string `url:"value" json:"value"`
	VariableType *string `url:"variable_type,omitempty" json:"variable_type,omitempty"`
}

// CreatePipelineScheduleVariable creates a pipeline schedule variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
func (s *PipelineSchedulesService) CreatePipelineScheduleVariable(pid interface{}, schedule int, opt *CreatePipelineScheduleVariableOptions, options ...RequestOptionFunc) (*PipelineVariable, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables", PathEscape(project), schedule)

	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineVariable)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// EditPipelineScheduleVariableOptions represents the available
// EditPipelineScheduleVariable() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule-variable
type EditPipelineScheduleVariableOptions struct {
	Value        *string `url:"value" json:"value"`
	VariableType *string `url:"variable_type,omitempty" json:"variable_type,omitempty"`
}

// EditPipelineScheduleVariable creates a pipeline schedule variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule-variable
func (s *PipelineSchedulesService) EditPipelineScheduleVariable(pid interface{}, schedule int, key string, opt *EditPipelineScheduleVariableOptions, options ...RequestOptionFunc) (*PipelineVariable, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables/%s", PathEscape(project), schedule, key)

	req, err := s.client.NewRequest(http.MethodPut, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineVariable)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// DeletePipelineScheduleVariable creates a pipeline schedule variable.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipeline_schedules.html#delete-a-pipeline-schedule-variable
func (s *PipelineSchedulesService) DeletePipelineScheduleVariable(pid interface{}, schedule int, key string, options ...RequestOptionFunc) (*PipelineVariable, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables/%s", PathEscape(project), schedule, key)

	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineVariable)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}
<|endoftext|>


<commit_before>package mmatcher

type Record struct {
	Id   string
	Atts []Atter
}

func (a *Record) IsMatch(b *Record, positions ...int) bool {
	if len(positions) <= 0 {
		positions = make([]int, len(a.Atts))
		for i := range positions {
			positions[i] = i
		}
	}
	e := make([]Atter, len(a.Atts))
	return a.IsMatchWithRanges(b, e, positions...)
}

func (a *Record) IsMatchWithRanges(b *Record, e []Atter, positions ...int) bool {
	if len(a.Atts) != len(b.Atts) || len(e) != len(a.Atts) {
		return false
	}
	if len(positions) <= 0 {
		positions = make([]int, len(a.Atts))
		for i := range positions {
			positions[i] = i
		}
	}
	matches := make([]bool, len(positions))
	for i, n := range positions {
		if n >= len(a.Atts) {
			return false
		}
		matches[i] = a.isMatchAt(b, e[n], n)
	}
	for _, m := range matches {
		if !m {
			return false
		}
	}
	return true
}

func (a *Record) isMatchAt(b *Record, e Atter, i int) bool {
	if i >= 0 && i < len(a.Atts) && i < len(b.Atts) {
		return a.Atts[i].Equal(b.Atts[i], e)
	}
	return false
}

type Records []Record

func (a *Record) MatchesAll(r Records, e ...Atter) []int {
	positions := make([]int, len(a.Atts))
	for i := range a.Atts {
		positions[i] = i
	}
	return a.Matches(r, positions, e...)
}

func (a *Record) Matches(r Records, positions []int, e ...Atter) (matches []int) {
	if len(e) <= 0 {
		e = make([]Atter, len(a.Atts))
	}
	for i, b := range r {
		if a.IsMatchWithRanges(&b, e, positions...) {
			matches = append(matches, i)
		}
	}
	return
}
<commit_msg>Add some commenting/documentation<commit_after>package mmatcher

// A Record holds a data to be matched based on attributes in Atts
type Record struct {
	ID   string
	Atts []Atter
}

// IsMatch returns true if Record a matches b exactly in columns given by positions
func (a *Record) IsMatch(b *Record, positions ...int) bool {
	if len(positions) <= 0 {
		positions = make([]int, len(a.Atts))
		for i := range positions {
			positions[i] = i
		}
	}
	e := make([]Atter, len(a.Atts))
	return a.IsMatchWithRanges(b, e, positions...)
}

// IsMatchWithRanges returns true if Record a matches b in columns specified in
// positions. e is a slice of Atters to use for +/- range comparisons in columns
// of the same index
func (a *Record) IsMatchWithRanges(b *Record, e []Atter, positions ...int) bool {
	if len(a.Atts) != len(b.Atts) || len(e) != len(a.Atts) {
		return false
	}
	if len(positions) <= 0 {
		positions = make([]int, len(a.Atts))
		for i := range positions {
			positions[i] = i
		}
	}
	matches := make([]bool, len(positions))
	for i, n := range positions {
		if n >= len(a.Atts) {
			return false
		}
		matches[i] = a.isMatchAt(b, e[n], n)
	}
	for _, m := range matches {
		if !m {
			return false
		}
	}
	return true
}

// isMatchAt returns true if single attribute column in i matches between
// a & b with given +/- range e
func (a *Record) isMatchAt(b *Record, e Atter, i int) bool {
	if i >= 0 && i < len(a.Atts) && i < len(b.Atts) {
		return a.Atts[i].Equal(b.Atts[i], e)
	}
	return false
}

// Records is just a slice of Record types
type Records []Record

// MatchesAll returns a slice containing the indices of r that match to a with
// the given +/- ranges in e
func (a *Record) MatchesAll(r Records, e ...Atter) []int {
	positions := make([]int, len(a.Atts))
	for i := range a.Atts {
		positions[i] = i
	}
	return a.Matches(r, positions, e...)
}

// Matches retruns a slice containing the indices from r that match to a at
// attributes in positions with any given +/- ranges in e
func (a *Record) Matches(r Records, positions []int, e ...Atter) (matches []int) {
	if len(e) <= 0 {
		e = make([]Atter, len(a.Atts))
	}
	for i, b := range r {
		if a.IsMatchWithRanges(&b, e, positions...) {
			matches = append(matches, i)
		}
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
    "reflect"
    "fmt"
    "unsafe"
    "github.com/BluePecker/JwtAuth/storage"
    _ "github.com/BluePecker/JwtAuth/storage/redis"
)

type Hand struct {

}

type Foot struct {
    K string
}

func (f *Foot) Hi() {
    fmt.Println("fh")
}

type User struct {
    Name string
    //Hand *Hand
    Foot *Foot
}

func (u *User) Ec() string {
    fmt.Println("ec")
    return "ec"
}

type G struct {
    Name string
}

func (g *G) E() {
    *g = G{
        Name: "shuc",
    }
}

func main() {
    user := &User{
        Name: "SC",
    }

    fmt.Printf("测试: %+v", (*struct {
        Ne  string
        Age int
        H   int
    })(unsafe.Pointer(user)))

    fmt.Println(reflect.ValueOf(*user).FieldByName("Foot"))

    fmt.Println(reflect.New(reflect.ValueOf(*user).Type()))

    redis, err := storage.NewManager("redis", storage.Options{
        Host: "127.0.0.1",
        Port: 6379,
        PoolSize: 20,
    });

    if err != nil {
        fmt.Println(err)
    }

    redis.Write("jwt", "13658009009", 0)
    redis.Write("auth", "23658009009", 30)

    fmt.Println("redis ttl: ", redis.TTL("jwt"))
    fmt.Println("redis ttl: ", redis.TTL("auth"))
    v, err := redis.ReadString("jwt")
    fmt.Println("redis ttl: ", v, err)
    v, err = redis.ReadString("auth")
    fmt.Println("redis ttl: ", v, err)

    //store := &storage.MemStore{}
    //
    ////store.SetImmutable("name", "shuchao", 3)
    //store.Set("name", "hi", 2)
    //
    //fmt.Println(store)
    //time.Sleep(time.Duration(1 * time.Second))
    //fmt.Println(store)
    //
    //store.Set("name", "me", 0)
    //fmt.Println(store)
    //time.Sleep(time.Duration(5000 * time.Second))
    //fmt.Println(store)
}

<commit_msg>redis driver<commit_after>package main

import (
    "reflect"
    "fmt"
    "unsafe"
    "github.com/BluePecker/JwtAuth/storage"
    _ "github.com/BluePecker/JwtAuth/storage/redis"
    "time"
)

type Hand struct {

}

type Foot struct {
    K string
}

func (f *Foot) Hi() {
    fmt.Println("fh")
}

type User struct {
    Name string
    //Hand *Hand
    Foot *Foot
}

func (u *User) Ec() string {
    fmt.Println("ec")
    return "ec"
}

type G struct {
    Name string
}

func (g *G) E() {
    *g = G{
        Name: "shuc",
    }
}

func main() {
    user := &User{
        Name: "SC",
    }

    fmt.Printf("测试: %+v", (*struct {
        Ne  string
        Age int
        H   int
    })(unsafe.Pointer(user)))

    fmt.Println(reflect.ValueOf(*user).FieldByName("Foot"))

    fmt.Println(reflect.New(reflect.ValueOf(*user).Type()))

    redis, err := storage.NewManager("redis", storage.Options{
        Host: "127.0.0.1",
        Port: 6379,
        PoolSize: 20,
    });

    if err != nil {
        fmt.Println(err)
    }

    redis.Write("jwt", "13658009009", 0)
    redis.Write("auth", "23658009009", 30)

    fmt.Println("redis ttl: ", redis.TTL("jwt"))
    fmt.Println("redis ttl: ", redis.TTL("auth"))
    v, err := redis.ReadString("jwt")
    fmt.Println("redis jwt: ", v, err)
    v, err = redis.ReadString("auth")
    fmt.Println("redis auth: ", v, err)

    time.Sleep(time.Duration(15) * time.Second)
    v, err = redis.ReadString("auth")
    fmt.Println("redis auth: ", v, err)

    //store := &storage.MemStore{}
    //
    ////store.SetImmutable("name", "shuchao", 3)
    //store.Set("name", "hi", 2)
    //
    //fmt.Println(store)
    //time.Sleep(time.Duration(1 * time.Second))
    //fmt.Println(store)
    //
    //store.Set("name", "me", 0)
    //fmt.Println(store)
    //time.Sleep(time.Duration(5000 * time.Second))
    //fmt.Println(store)
}

<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io"
	"log"
	"net/http"
)

var addr = flag.String("127.0.0.1", ":1718", "http service address") // Q=17, R=18

func main() {
	flag.Parse()
	//http.Handle("/", http.HandlerFunc(QR))
	err := http.ListenAndServe(*addr, http.HandlerFunc(mainHandler))
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

func mainHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method != "POST" {
		io.WriteString(w, "please use post method.
")
		io.WriteString(w, req.URL.Path)
		req.Host = "www.qq.com"
		//templ.Execute(w, req.FormValue("s"))
		return
	}
}

/*
func QR(w http.ResponseWriter, req *http.Request) {
	if req.Method != "POST" {
		io.WriteString(w, "please use post method.")
		//templ.Execute(w, req.FormValue("s"))
		return
	}
}

const templateStr = `

<title>QR Link Generator</title>

{{if .}}

{{.}}

{{end}}
<form action="/" name=f method="GET"><input maxLength=1024 size=70
name=s value="" title="Text to QR Encode"><input type=submit
value="Show QR" name=qr>
</form>

`
*/
<commit_msg>before testing reverseproxy<commit_after>package main

import (
	"flag"
	//"fmt"
	"io"
	//"io/ioutil"
	"log"
	"net/http"
)

var addr = flag.String("127.0.0.1", ":1718", "http service address") // Q=17, R=18

func main() {
	flag.Parse()
	//http.Handle("/", http.HandlerFunc(QR))
	err := http.ListenAndServe(*addr, http.HandlerFunc(mainHandler))
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

func mainHandler(w http.ResponseWriter, req *http.Request) {
	if req.Method != "POST" {
		//io.WriteString(w, "please use post method.
")
		//io.WriteString(w, req.URL.Path)
		//log.Println("https://weixin.qq.com" + req.URL.Path)
		newReq, err := http.NewRequest(req.Method, "https://weixin.qq.com"+req.URL.Path, nil)
		//newReq.URL.Host = "weixin.qq.com"
		//newReq.Host = newReq.URL.Host
		//newReq.URL.Path = req.URL.Path

		client := &http.Client{}
		resp, err := client.Do(newReq)

		if err != nil {
			log.Fatal(err.Error())
		}

		defer resp.Body.Close()

		//body, err := ioutil.ReadAll(resp.Body)
		//log.Println(string(body))

		io.Copy(w, resp.Body)

		if err != nil {
			log.Fatal(err.Error())
		}
		return
	}
}

/*
func QR(w http.ResponseWriter, req *http.Request) {
	if req.Method != "POST" {
		io.WriteString(w, "please use post method.")
		//templ.Execute(w, req.FormValue("s"))
		return
	}
}

const templateStr = `

<title>QR Link Generator</title>

{{if .}}

{{.}}

{{end}}
<form action="/" name=f method="GET"><input maxLength=1024 size=70
name=s value="" title="Text to QR Encode"><input type=submit
value="Show QR" name=qr>
</form>

`
*/
<|endoftext|>


<commit_before>/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/
/**********************************************************\
 *                                                        *
 * io/reader.go                                           *
 *                                                        *
 * hprose reader for Go.                                  *
 *                                                        *
 * LastModified: Sep 6, 2016                              *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
\**********************************************************/

package io

import (
	"bytes"
	"errors"
)

// Reader is a fine-grained operation struct for Hprose unserialization
// when JSONCompatible is true, the Map data will unserialize to map[string]interface as the default type
type Reader struct {
	RawReader
	Simple         bool
	structRef      []interface{}
	fieldsRef      [][]string
	ref            []interface{}
	JSONCompatible bool
}

// NewReader is the constructor for Hprose Reader
func NewReader(buf []byte, simple bool) (reader *Reader) {
	reader = new(Reader)
	reader.buf = buf
	reader.Simple = simple
	return
}

// CheckTag the next byte in reader is the expected tag or not
func (r *Reader) CheckTag(expectTag byte) (tag byte) {
	tag = r.readByte()
	if tag != expectTag {
		unexpectedTag(tag, []byte{expectTag})
	}
	return
}

// CheckTags the next byte in reader in the expected tags
func (r *Reader) CheckTags(expectTags []byte) (tag byte) {
	tag = r.readByte()
	if bytes.IndexByte(expectTags, tag) == -1 {
		unexpectedTag(tag, expectTags)
	}
	return
}

// ReadBool from the reader
func (r *Reader) ReadBool() bool {
	tag := r.readByte()
	decoder := boolDecoders[tag]
	if decoder != nil {
		return decoder(r)
	}
	castError(tag, "bool")
	return false
}

// ReadStringWithoutTag from the reader
func (r *Reader) ReadStringWithoutTag() (str string) {
	str = readString(&r.ByteReader)
	if !r.Simple {
		setReaderRef(r, str)
	}
	return str
}

// ReadString from the reader
func (r *Reader) ReadString() (str string) {
	return ""
}

// ReadRef from the reader
func (r *Reader) ReadRef() interface{} {
	if r.Simple {
		panic(errors.New("reference unserialization can't support in simple mode"))
	}
	return readRef(r, readInt(&r.ByteReader))
}

// private function

func setReaderRef(r *Reader, o interface{}) {
	if r.ref == nil {
		r.ref = make([]interface{}, 0, 64)
	}
	r.ref = append(r.ref, o)
}

func readRef(r *Reader, i int) interface{} {
	return r.ref[i]
}

func resetReaderRef(r *Reader) {
	if r.ref != nil {
		r.ref = r.ref[:0]
	}
}

func tagToString(tag byte) string {
	switch tag {
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	case TagInteger:
		return "int"
	case TagLong:
		return "big.Int"
	case TagDouble:
		return "float64"
	case TagNull:
		return "nil"
	case TagEmpty:
		return "empty string"
	case TagTrue:
		return "true"
	case TagFalse:
		return "false"
	case TagNaN:
		return "NaN"
	case TagInfinity:
		return "Infinity"
	case TagDate:
		return "time.Time"
	case TagTime:
		return "time.Time"
	case TagBytes:
		return "[]byte"
	case TagUTF8Char:
		return "string"
	case TagString:
		return "string"
	case TagGUID:
		return "GUID"
	case TagList:
		return "slice"
	case TagMap:
		return "map"
	case TagClass:
		return "struct"
	case TagObject:
		return "struct"
	case TagRef:
		return "reference"
	default:
		unexpectedTag(tag, nil)
	}
	return ""
}
func castError(tag byte, descType string) {
	srcType := tagToString(tag)
	panic(errors.New("can't convert " + srcType + " to " + descType))
}
<commit_msg>Improved tagToString<commit_after>/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/
/**********************************************************\
 *                                                        *
 * io/reader.go                                           *
 *                                                        *
 * hprose reader for Go.                                  *
 *                                                        *
 * LastModified: Sep 6, 2016                              *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
\**********************************************************/

package io

import (
	"bytes"
	"errors"
)

// Reader is a fine-grained operation struct for Hprose unserialization
// when JSONCompatible is true, the Map data will unserialize to map[string]interface as the default type
type Reader struct {
	RawReader
	Simple         bool
	structRef      []interface{}
	fieldsRef      [][]string
	ref            []interface{}
	JSONCompatible bool
}

// NewReader is the constructor for Hprose Reader
func NewReader(buf []byte, simple bool) (reader *Reader) {
	reader = new(Reader)
	reader.buf = buf
	reader.Simple = simple
	return
}

// CheckTag the next byte in reader is the expected tag or not
func (r *Reader) CheckTag(expectTag byte) (tag byte) {
	tag = r.readByte()
	if tag != expectTag {
		unexpectedTag(tag, []byte{expectTag})
	}
	return
}

// CheckTags the next byte in reader in the expected tags
func (r *Reader) CheckTags(expectTags []byte) (tag byte) {
	tag = r.readByte()
	if bytes.IndexByte(expectTags, tag) == -1 {
		unexpectedTag(tag, expectTags)
	}
	return
}

// ReadBool from the reader
func (r *Reader) ReadBool() bool {
	tag := r.readByte()
	decoder := boolDecoders[tag]
	if decoder != nil {
		return decoder(r)
	}
	castError(tag, "bool")
	return false
}

// ReadStringWithoutTag from the reader
func (r *Reader) ReadStringWithoutTag() (str string) {
	str = readString(&r.ByteReader)
	if !r.Simple {
		setReaderRef(r, str)
	}
	return str
}

// ReadString from the reader
func (r *Reader) ReadString() (str string) {
	return ""
}

// ReadRef from the reader
func (r *Reader) ReadRef() interface{} {
	if r.Simple {
		panic(errors.New("reference unserialization can't support in simple mode"))
	}
	return readRef(r, readInt(&r.ByteReader))
}

// private function

func setReaderRef(r *Reader, o interface{}) {
	if r.ref == nil {
		r.ref = make([]interface{}, 0, 64)
	}
	r.ref = append(r.ref, o)
}

func readRef(r *Reader, i int) interface{} {
	return r.ref[i]
}

func resetReaderRef(r *Reader) {
	if r.ref != nil {
		r.ref = r.ref[:0]
	}
}

var tagStringMap = map[byte]string{
	'0':         "int",
	'1':         "int",
	'2':         "int",
	'3':         "int",
	'4':         "int",
	'5':         "int",
	'6':         "int",
	'7':         "int",
	'8':         "int",
	'9':         "int",
	TagInteger:  "int",
	TagLong:     "big.Int",
	TagDouble:   "float64",
	TagNull:     "nil",
	TagEmpty:    "empty string",
	TagTrue:     "true",
	TagFalse:    "false",
	TagNaN:      "NaN",
	TagInfinity: "Infinity",
	TagDate:     "time.Time",
	TagTime:     "time.Time",
	TagBytes:    "[]byte",
	TagUTF8Char: "string",
	TagString:   "string",
	TagGUID:     "GUID",
	TagList:     "slice",
	TagMap:      "map",
	TagClass:    "struct",
	TagObject:   "struct",
	TagRef:      "reference",
}

func tagToString(tag byte) (str string) {
	str = tagStringMap[tag]
	if str == "" {
		unexpectedTag(tag, nil)
	}
	return
}

func castError(tag byte, descType string) {
	srcType := tagToString(tag)
	panic(errors.New("can't convert " + srcType + " to " + descType))
}
<|endoftext|>


<commit_before>package clogger

import (
	"fmt"
	"io"
	"log"
	"time"
)

type writerLogger struct {
	logger *log.Logger
	baseIo io.Closer
	level  int
}

func CreateIoWriter(target io.WriteCloser) Logger {
	logger := log.New(target, "", 0)
	return &writerLogger{logger: logger, baseIo: target, level: Warning}
}

func (l *writerLogger) log(level int, message string) {
	if l.level < level {
		return
	}

	var severity string

	switch level {
	case 1:
		severity = "FATAL"
	case 2:
		severity = "ERROR"
	case 3:
		severity = "WARNING"
	case 4:
		severity = "INFO"
	case 5:
		severity = "DEBUG"
	}

	now := time.Now()
	timestamp := now.Format("Mon 01 15:04:05")

	l.logger.Printf("%v: %v: %v", timestamp, severity, message)
}

func (l *writerLogger) Debug(format string, args ...interface{}) {
	message := fmt.Sprintf(format, args...)
	l.log(Debug, message)
}

func (l *writerLogger) Info(format string, args ...interface{}) {
	message := fmt.Sprintf(format, args...)
	l.log(Info, message)
}

func (l *writerLogger) Warning(format string, args ...interface{}) {
	message := fmt.Sprintf(format, args...)
	l.log(Warning, message)
}

func (l *writerLogger) Error(format string, args ...interface{}) {
	message := fmt.Sprintf(format, args...)
	l.log(Error, message)
}

func (l *writerLogger) Fatal(format string, args ...interface{}) {
	message := fmt.Sprintf(format, args...)
	l.log(Fatal, message)
}

func (l *writerLogger) SetLevel(level int) {
	l.level = level
}

func (l *writerLogger) Close() {
	l.baseIo.Close()
}
<commit_msg>better basic interface compatibility<commit_after>package clogger

import (
	"fmt"
	"io"
	"log"
	"time"
)

type writerLogger struct {
	logger *log.Logger
	baseIo io.Writer
	level  int
}

func CreateIoWriter(target io.Writer) Logger {
	logger := log.New(target, "", 0)
	return &writerLogger{logger: logger, baseIo: target, level: Warning}
}

func (l *writerLogger) log(level int, message string) {
	if l.level < level {
		return
	}

	var severity string

	switch level {
	case 1:
		severity = "FATAL"
	case 2:
		severity = "ERROR"
	case 3:
		severity = "WARNING"
	case 4:
		severity = "INFO"
	case 5:
		severity = "DEBUG"
	}

	now := time.Now()
	timestamp := now.Format("Mon 01 15:04:05")

	l.logger.Printf("%v: %v: %v", timestamp, severity, message)
}

func (l *writerLogger) Debug(format string, args ...interface{}) {
	message := fmt.Sprintf(format, args...)
	l.log(Debug, message)
}

func (l *writerLogger) Info(format string, args ...interface{}) {
	message := fmt.Sprintf(format, args...)
	l.log(Info, message)
}

func (l *writerLogger) Warning(format string, args ...interface{}) {
	message := fmt.Sprintf(format, args...)
	l.log(Warning, message)
}

func (l *writerLogger) Error(format string, args ...interface{}) {
	message := fmt.Sprintf(format, args...)
	l.log(Error, message)
}

func (l *writerLogger) Fatal(format string, args ...interface{}) {
	message := fmt.Sprintf(format, args...)
	l.log(Fatal, message)
}

func (l *writerLogger) SetLevel(level int) {
	l.level = level
}

func (l *writerLogger) Close() {
	closer, ok := l.baseIo.(io.Closer)
	if ok {
		closer.Close()
	}
}
<|endoftext|>


<commit_before>package apps

import (
	"encoding/json"
	"io"
	"net/url"
	"regexp"
	"time"

	"github.com/cozy/cozy-stack/pkg/hooks"
	"github.com/cozy/cozy-stack/pkg/logger"
	"github.com/cozy/cozy-stack/pkg/prefixer"
	"github.com/cozy/cozy-stack/pkg/realtime"
	"github.com/cozy/cozy-stack/pkg/utils"
	"github.com/sirupsen/logrus"
)

var slugReg = regexp.MustCompile(`^[a-z0-9\-]+$`)

// Operation is the type of operation the installer is created for.
type Operation int

const (
	// Install operation for installing an application
	Install Operation = iota + 1
	// Update operation for updating an application
	Update
	// Delete operation for deleting an application
	Delete
)

// Installer is used to install or update applications.
type Installer struct {
	fetcher  Fetcher
	op       Operation
	fs       Copier
	db       prefixer.Prefixer
	endState State

	overridenParameters *json.RawMessage
	permissionsAcked    bool

	man  Manifest
	src  *url.URL
	slug string

	errc chan error
	manc chan Manifest
	log  *logrus.Entry
}

// InstallerOptions provides the slug name of the application along with the
// source URL.
type InstallerOptions struct {
	Type             AppType
	Operation        Operation
	Manifest         Manifest
	Slug             string
	SourceURL        string
	Deactivated      bool
	PermissionsAcked bool
	Registries       []*url.URL

	// Used to override the "Parameters" field of konnectors during installation.
	// This modification is useful to allow the parameterization of a konnector
	// at its installation as we do not have yet a registry up and running.
	OverridenParameters *json.RawMessage
}

// Fetcher interface should be implemented by the underlying transport
// used to fetch the application data.
type Fetcher interface {
	// FetchManifest should returns an io.ReadCloser to read the
	// manifest data
	FetchManifest(src *url.URL) (io.ReadCloser, error)
	// Fetch should download the application and install it in the given
	// directory.
	Fetch(src *url.URL, fs Copier, man Manifest) error
}

// NewInstaller creates a new Installer
func NewInstaller(db prefixer.Prefixer, fs Copier, opts *InstallerOptions) (*Installer, error) {
	man, err := initManifest(db, opts)
	if err != nil {
		return nil, err
	}

	var src *url.URL
	switch opts.Operation {
	case Install:
		if opts.SourceURL == "" {
			return nil, ErrMissingSource
		}
		src, err = url.Parse(opts.SourceURL)
	case Update, Delete:
		var srcString string
		if opts.SourceURL == "" {
			srcString = man.Source()
		} else {
			srcString = opts.SourceURL
		}
		src, err = url.Parse(srcString)
	default:
		panic("Unknown installer operation")
	}
	if err != nil {
		return nil, err
	}

	var endState State
	if opts.Deactivated || man.State() == Installed {
		endState = Installed
	} else {
		endState = Ready
	}

	log := logger.WithDomain(db.DomainName()).WithField("nspace", "apps")

	var manFilename string
	switch man.AppType() {
	case Webapp:
		manFilename = WebappManifestName
	case Konnector:
		manFilename = KonnectorManifestName
	}

	var fetcher Fetcher
	switch src.Scheme {
	case "git", "git+ssh", "ssh+git":
		fetcher = newGitFetcher(manFilename, log)
	case "http", "https":
		fetcher = newHTTPFetcher(manFilename, log)
	case "registry":
		fetcher = newRegistryFetcher(opts.Registries, log)
	case "file":
		fetcher = newFileFetcher(manFilename, log)
	default:
		return nil, ErrNotSupportedSource
	}

	return &Installer{
		fetcher:  fetcher,
		op:       opts.Operation,
		db:       db,
		fs:       fs,
		endState: endState,

		overridenParameters: opts.OverridenParameters,
		permissionsAcked:    opts.PermissionsAcked,

		man:  man,
		src:  src,
		slug: man.Slug(),

		errc: make(chan error, 1),
		manc: make(chan Manifest, 2),
		log:  log,
	}, nil
}

func initManifest(db prefixer.Prefixer, opts *InstallerOptions) (man Manifest, err error) {
	if man = opts.Manifest; man != nil {
		return man, nil
	}

	slug := opts.Slug
	if slug == "" || !slugReg.MatchString(slug) {
		return nil, ErrInvalidSlugName
	}

	if opts.Operation == Install {
		_, err = GetBySlug(db, slug, opts.Type)
		if err == nil {
			return nil, ErrAlreadyExists
		}
		if err != ErrNotFound {
			return nil, err
		}
		switch opts.Type {
		case Webapp:
			man = &WebappManifest{DocSlug: slug}
		case Konnector:
			man = &KonnManifest{DocSlug: slug}
		}
	} else {
		man, err = GetBySlug(db, slug, opts.Type)
		if err != nil {
			return nil, err
		}
	}

	if man == nil {
		panic("Bad or missing installer type")
	}

	return man, nil
}

// Slug return the slug of the application being installed.
func (i *Installer) Slug() string {
	return i.slug
}

// Domain return the domain of instance associated with the installer.
func (i *Installer) Domain() string {
	return i.db.DomainName()
}

// Run will install, update or delete the application linked to the installer,
// depending on specified operation. It will report its progress or error (see
// Poll method) and should be run asynchronously.
func (i *Installer) Run() {
	var err error

	if i.man == nil {
		panic("Manifest is nil")
	}

	switch i.op {
	case Install:
		err = i.install()
	case Update:
		err = i.update()
	case Delete:
		err = i.delete()
	default:
		panic("Unknown operation")
	}

	man := i.man.Clone().(Manifest)
	if err != nil {
		man.SetError(err)
		realtime.GetHub().Publish(i.db, realtime.EventUpdate, man.Clone(), nil)
	}
	i.manc <- man
}

// RunSync does the same work as Run but can be used synchronously.
func (i *Installer) RunSync() (Manifest, error) {
	go i.Run()
	for {
		man, done, err := i.Poll()
		if err != nil {
			return nil, err
		}
		if done {
			return man, nil
		}
	}
}

// install will perform the installation of an application. It returns the
// freshly fetched manifest from the source along with a possible error in case
// the installation went wrong.
//
// Note that the fetched manifest is returned even if an error occurred while
// upgrading.
func (i *Installer) install() error {
	i.log.Infof("Start install: %s %s", i.slug, i.src.String())
	args := []string{i.db.DomainName(), i.slug}
	return hooks.Execute("install-app", args, func() error {
		newManifest, err := i.ReadManifest(Installing)
		if err != nil {
			return err
		}
		i.man = newManifest
		i.sendRealtimeEvent()
		i.manc <- i.man.Clone().(Manifest)
		if err := i.fetcher.Fetch(i.src, i.fs, i.man); err != nil {
			return err
		}
		i.man.SetState(i.endState)
		return i.man.Create(i.db)
	})
}

// update will perform the update of an already installed application. It
// returns the freshly fetched manifest from the source along with a possible
// error in case the update went wrong.
//
// Note that the fetched manifest is returned even if an error occurred while
// upgrading.
func (i *Installer) update() error {
	i.log.Infof("Start update: %s %s", i.slug, i.src.String())
	if err := i.checkState(i.man); err != nil {
		return err
	}

	oldManifest := i.man
	newManifest, err := i.ReadManifest(Upgrading)
	if err != nil {
		return err
	}

	// Fast path for registry:// and http:// sources: we do not need to go
	// further in the case where the fetched manifest has the same version has
	// the one in database.
	//
	// For git:// and file:// sources, it may be more complicated since we need
	// to actually fetch the data to extract the exact version of the manifest.
	makeUpdate := true
	switch i.src.Scheme {
	case "registry", "http", "https":
		makeUpdate = (newManifest.Version() != oldManifest.Version())
	}

	// Check the possible permissions changes before updating. If the
	// verifyPermissions flag is activated (for non manual updates for example),
	// we cancel out the update and mark the UpdateAvailable field of the
	// application instead of actually updating.
	if makeUpdate && !isPlatformApp(oldManifest) {
		oldPermissions := oldManifest.Permissions()
		newPermissions := newManifest.Permissions()
		samePermissions := newPermissions != nil && oldPermissions != nil &&
			newPermissions.HasSameRules(oldPermissions)
		if !samePermissions && !i.permissionsAcked {
			makeUpdate = false
		}
	}

	if makeUpdate {
		i.man = newManifest
		i.sendRealtimeEvent()
		i.manc <- i.man.Clone().(Manifest)
		if err := i.fetcher.Fetch(i.src, i.fs, i.man); err != nil {
			return err
		}
		i.man.SetState(i.endState)
	} else {
		i.man.SetAvailableVersion(newManifest.Version())
		i.sendRealtimeEvent()
		i.manc <- i.man.Clone().(Manifest)
	}

	return i.man.Update(i.db)
}

func (i *Installer) delete() error {
	i.log.Infof("Start delete: %s %s", i.slug, i.src.String())
	if err := i.checkState(i.man); err != nil {
		return err
	}
	args := []string{i.db.DomainName(), i.slug}
	return hooks.Execute("uninstall-app", args, func() error {
		return i.man.Delete(i.db)
	})
}

// checkState returns whether or not the manifest is in the right state to
// perform an update or deletion.
func (i *Installer) checkState(man Manifest) error {
	state := man.State()
	if state == Ready || state == Installed {
		return nil
	}
	if time.Since(man.LastUpdate()) > 15*time.Minute {
		return nil
	}
	return ErrBadState
}

// ReadManifest will fetch the manifest and read its JSON content into the
// passed manifest pointer.
//
// The State field of the manifest will be set to the specified state.
func (i *Installer) ReadManifest(state State) (Manifest, error) {
	r, err := i.fetcher.FetchManifest(i.src)
	if err != nil {
		return nil, err
	}
	defer r.Close()

	newManifest, err := i.man.ReadManifest(io.LimitReader(r, ManifestMaxSize), i.slug, i.src.String())
	if err != nil {
		return nil, err
	}
	newManifest.SetState(state)

	shouldOverrideParameters := (i.overridenParameters != nil &&
		i.man.AppType() == Konnector &&
		i.src.Scheme != "registry")
	if shouldOverrideParameters {
		if m, ok := newManifest.(*KonnManifest); ok {
			m.Parameters = i.overridenParameters
		}
	}
	return newManifest, nil
}

func (i *Installer) sendRealtimeEvent() {
	realtime.GetHub().Publish(i.db, realtime.EventUpdate, i.man.Clone(), nil)
}

// Poll should be used to monitor the progress of the Installer.
func (i *Installer) Poll() (Manifest, bool, error) {
	man := <-i.manc
	done := false
	if s := man.State(); s == Ready || s == Installed || s == Errored {
		done = true
	}
	return man, done, man.Error()
}

func isPlatformApp(man Manifest) bool {
	if man.AppType() != Webapp {
		return false
	}
	return utils.IsInArray(man.Slug(), []string{
		"onboarding",
		"settings",
		"collect",
		"home",
		"photos",
		"drive",
		"store",
		"banks",
		"contacts",
	})
}
<commit_msg>Installer: improve sync mode and add a lazy update method<commit_after>package apps

import (
	"encoding/json"
	"io"
	"net/url"
	"regexp"
	"time"

	"github.com/cozy/cozy-stack/pkg/hooks"
	"github.com/cozy/cozy-stack/pkg/logger"
	"github.com/cozy/cozy-stack/pkg/prefixer"
	"github.com/cozy/cozy-stack/pkg/realtime"
	"github.com/cozy/cozy-stack/pkg/registry"
	"github.com/cozy/cozy-stack/pkg/utils"
	"github.com/sirupsen/logrus"
)

var slugReg = regexp.MustCompile(`^[a-z0-9\-]+$`)

// Operation is the type of operation the installer is created for.
type Operation int

const (
	// Install operation for installing an application
	Install Operation = iota + 1
	// Update operation for updating an application
	Update
	// Delete operation for deleting an application
	Delete
)

// Installer is used to install or update applications.
type Installer struct {
	fetcher  Fetcher
	op       Operation
	fs       Copier
	db       prefixer.Prefixer
	endState State

	overridenParameters *json.RawMessage
	permissionsAcked    bool

	man  Manifest
	src  *url.URL
	slug string

	manc chan Manifest
	log  *logrus.Entry
}

// InstallerOptions provides the slug name of the application along with the
// source URL.
type InstallerOptions struct {
	Type             AppType
	Operation        Operation
	Manifest         Manifest
	Slug             string
	SourceURL        string
	Deactivated      bool
	PermissionsAcked bool
	Registries       []*url.URL

	// Used to override the "Parameters" field of konnectors during installation.
	// This modification is useful to allow the parameterization of a konnector
	// at its installation as we do not have yet a registry up and running.
	OverridenParameters *json.RawMessage
}

// Fetcher interface should be implemented by the underlying transport
// used to fetch the application data.
type Fetcher interface {
	// FetchManifest should returns an io.ReadCloser to read the
	// manifest data
	FetchManifest(src *url.URL) (io.ReadCloser, error)
	// Fetch should download the application and install it in the given
	// directory.
	Fetch(src *url.URL, fs Copier, man Manifest) error
}

// NewInstaller creates a new Installer
func NewInstaller(db prefixer.Prefixer, fs Copier, opts *InstallerOptions) (*Installer, error) {
	man, err := initManifest(db, opts)
	if err != nil {
		return nil, err
	}

	var src *url.URL
	switch opts.Operation {
	case Install:
		if opts.SourceURL == "" {
			return nil, ErrMissingSource
		}
		src, err = url.Parse(opts.SourceURL)
	case Update, Delete:
		var srcString string
		if opts.SourceURL == "" {
			srcString = man.Source()
		} else {
			srcString = opts.SourceURL
		}
		src, err = url.Parse(srcString)
	default:
		panic("Unknown installer operation")
	}
	if err != nil {
		return nil, err
	}

	var endState State
	if opts.Deactivated || man.State() == Installed {
		endState = Installed
	} else {
		endState = Ready
	}

	log := logger.WithDomain(db.DomainName()).WithField("nspace", "apps")

	var manFilename string
	switch man.AppType() {
	case Webapp:
		manFilename = WebappManifestName
	case Konnector:
		manFilename = KonnectorManifestName
	}

	var fetcher Fetcher
	switch src.Scheme {
	case "git", "git+ssh", "ssh+git":
		fetcher = newGitFetcher(manFilename, log)
	case "http", "https":
		fetcher = newHTTPFetcher(manFilename, log)
	case "registry":
		fetcher = newRegistryFetcher(opts.Registries, log)
	case "file":
		fetcher = newFileFetcher(manFilename, log)
	default:
		return nil, ErrNotSupportedSource
	}

	return &Installer{
		fetcher:  fetcher,
		op:       opts.Operation,
		db:       db,
		fs:       fs,
		endState: endState,

		overridenParameters: opts.OverridenParameters,
		permissionsAcked:    opts.PermissionsAcked,

		man:  man,
		src:  src,
		slug: man.Slug(),

		manc: make(chan Manifest, 2),
		log:  log,
	}, nil
}

func initManifest(db prefixer.Prefixer, opts *InstallerOptions) (man Manifest, err error) {
	if man = opts.Manifest; man != nil {
		return man, nil
	}

	slug := opts.Slug
	if slug == "" || !slugReg.MatchString(slug) {
		return nil, ErrInvalidSlugName
	}

	if opts.Operation == Install {
		_, err = GetBySlug(db, slug, opts.Type)
		if err == nil {
			return nil, ErrAlreadyExists
		}
		if err != ErrNotFound {
			return nil, err
		}
		switch opts.Type {
		case Webapp:
			man = &WebappManifest{DocSlug: slug}
		case Konnector:
			man = &KonnManifest{DocSlug: slug}
		}
	} else {
		man, err = GetBySlug(db, slug, opts.Type)
		if err != nil {
			return nil, err
		}
	}

	if man == nil {
		panic("Bad or missing installer type")
	}

	return man, nil
}

// Slug return the slug of the application being installed.
func (i *Installer) Slug() string {
	return i.slug
}

// Domain return the domain of instance associated with the installer.
func (i *Installer) Domain() string {
	return i.db.DomainName()
}

// Run will install, update or delete the application linked to the installer,
// depending on specified operation. It will report its progress or error (see
// Poll method) and should be run asynchronously.
func (i *Installer) Run() {
	if err := i.run(); err != nil {
		i.man.SetError(err)
		realtime.GetHub().Publish(i.db, realtime.EventUpdate, i.man.Clone(), nil)
	}
	i.notifyChannel()
}

// RunSync does the same work as Run but can be used synchronously.
func (i *Installer) RunSync() (Manifest, error) {
	i.manc = nil
	if err := i.run(); err != nil {
		return nil, err
	}
	return i.man.Clone().(Manifest), nil
}

func (i *Installer) run() (err error) {
	if i.man == nil {
		panic("Manifest is nil")
	}
	switch i.op {
	case Install:
		return i.install()
	case Update:
		return i.update()
	case Delete:
		return i.delete()
	default:
		panic("Unknown operation")
	}
}

// install will perform the installation of an application. It returns the
// freshly fetched manifest from the source along with a possible error in case
// the installation went wrong.
//
// Note that the fetched manifest is returned even if an error occurred while
// upgrading.
func (i *Installer) install() error {
	i.log.Infof("Start install: %s %s", i.slug, i.src.String())
	args := []string{i.db.DomainName(), i.slug}
	return hooks.Execute("install-app", args, func() error {
		newManifest, err := i.ReadManifest(Installing)
		if err != nil {
			return err
		}
		i.man = newManifest
		i.sendRealtimeEvent()
		i.notifyChannel()
		if err := i.fetcher.Fetch(i.src, i.fs, i.man); err != nil {
			return err
		}
		i.man.SetState(i.endState)
		return i.man.Create(i.db)
	})
}

// update will perform the update of an already installed application. It
// returns the freshly fetched manifest from the source along with a possible
// error in case the update went wrong.
//
// Note that the fetched manifest is returned even if an error occurred while
// upgrading.
func (i *Installer) update() error {
	i.log.Infof("Start update: %s %s", i.slug, i.src.String())
	if err := i.checkState(i.man); err != nil {
		return err
	}

	oldManifest := i.man
	newManifest, err := i.ReadManifest(Upgrading)
	if err != nil {
		return err
	}

	// Fast path for registry:// and http:// sources: we do not need to go
	// further in the case where the fetched manifest has the same version has
	// the one in database.
	//
	// For git:// and file:// sources, it may be more complicated since we need
	// to actually fetch the data to extract the exact version of the manifest.
	makeUpdate := true
	switch i.src.Scheme {
	case "registry", "http", "https":
		makeUpdate = (newManifest.Version() != oldManifest.Version())
	}

	// Check the possible permissions changes before updating. If the
	// verifyPermissions flag is activated (for non manual updates for example),
	// we cancel out the update and mark the UpdateAvailable field of the
	// application instead of actually updating.
	if makeUpdate && !isPlatformApp(oldManifest) {
		oldPermissions := oldManifest.Permissions()
		newPermissions := newManifest.Permissions()
		samePermissions := newPermissions != nil && oldPermissions != nil &&
			newPermissions.HasSameRules(oldPermissions)
		if !samePermissions && !i.permissionsAcked {
			makeUpdate = false
		}
	}

	if makeUpdate {
		i.man = newManifest
		i.sendRealtimeEvent()
		i.notifyChannel()
		if err := i.fetcher.Fetch(i.src, i.fs, i.man); err != nil {
			return err
		}
		i.man.SetState(i.endState)
	} else {
		i.man.SetAvailableVersion(newManifest.Version())
		i.sendRealtimeEvent()
		i.notifyChannel()
	}

	return i.man.Update(i.db)
}

func (i *Installer) notifyChannel() {
	if i.manc != nil {
		i.manc <- i.man.Clone().(Manifest)
	}
}

func (i *Installer) delete() error {
	i.log.Infof("Start delete: %s %s", i.slug, i.src.String())
	if err := i.checkState(i.man); err != nil {
		return err
	}
	args := []string{i.db.DomainName(), i.slug}
	return hooks.Execute("uninstall-app", args, func() error {
		return i.man.Delete(i.db)
	})
}

// checkState returns whether or not the manifest is in the right state to
// perform an update or deletion.
func (i *Installer) checkState(man Manifest) error {
	state := man.State()
	if state == Ready || state == Installed {
		return nil
	}
	if time.Since(man.LastUpdate()) > 15*time.Minute {
		return nil
	}
	return ErrBadState
}

// ReadManifest will fetch the manifest and read its JSON content into the
// passed manifest pointer.
//
// The State field of the manifest will be set to the specified state.
func (i *Installer) ReadManifest(state State) (Manifest, error) {
	r, err := i.fetcher.FetchManifest(i.src)
	if err != nil {
		return nil, err
	}
	defer r.Close()

	newManifest, err := i.man.ReadManifest(io.LimitReader(r, ManifestMaxSize), i.slug, i.src.String())
	if err != nil {
		return nil, err
	}
	newManifest.SetState(state)

	shouldOverrideParameters := (i.overridenParameters != nil &&
		i.man.AppType() == Konnector &&
		i.src.Scheme != "registry")
	if shouldOverrideParameters {
		if m, ok := newManifest.(*KonnManifest); ok {
			m.Parameters = i.overridenParameters
		}
	}
	return newManifest, nil
}

func (i *Installer) sendRealtimeEvent() {
	realtime.GetHub().Publish(i.db, realtime.EventUpdate, i.man.Clone(), nil)
}

// Poll should be used to monitor the progress of the Installer.
func (i *Installer) Poll() (Manifest, bool, error) {
	man := <-i.manc
	done := false
	if s := man.State(); s == Ready || s == Installed || s == Errored {
		done = true
	}
	return man, done, man.Error()
}

func doLazyUpdate(db prefixer.Prefixer, man Manifest, copier Copier, registries []*url.URL) Manifest {
	src, err := url.Parse(man.Source())
	if err != nil || src.Scheme != "registry" {
		return man
	}
	v, errv := registry.GetLatestVersion(man.Slug(), getRegistryChannel(src), registries)
	if errv != nil || v.Version == man.Version() {
		return man
	}
	inst, err := NewInstaller(db, copier, &InstallerOptions{
		Operation:  Update,
		Manifest:   man,
		Registries: registries,
		SourceURL:  src.String(),
	})
	if err != nil {
		return man
	}
	newman, err := inst.RunSync()
	if err != nil {
		return man
	}
	return newman
}

func isPlatformApp(man Manifest) bool {
	if man.AppType() != Webapp {
		return false
	}
	return utils.IsInArray(man.Slug(), []string{
		"onboarding",
		"settings",
		"collect",
		"home",
		"photos",
		"drive",
		"store",
		"banks",
		"contacts",
	})
}
<|endoftext|>


<commit_before>package db

import (
	"bytes"
	"crypto/rand"
	"database/sql"
	"encoding/hex"
	"github.com/OpenBazaar/wallet-interface"
	"github.com/btcsuite/btcd/btcec"
	"testing"
)

var kdb KeysDB

func init() {
	conn, _ := sql.Open("sqlite3", ":memory:")
	initDatabaseTables(conn, "")
	kdb = KeysDB{
		db: conn,
	}
}

func TestGetAll(t *testing.T) {
	for i := 0; i < 100; i++ {
		b := make([]byte, 32)
		rand.Read(b)
		err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, i})
		if err != nil {
			t.Error(err)
		}
	}
	all, err := kdb.GetAll()
	if err != nil || len(all) != 100 {
		t.Error("Failed to fetch all keys")
	}
}

func TestPutKey(t *testing.T) {
	b := make([]byte, 32)
	err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, 0})
	if err != nil {
		t.Error(err)
	}
	stmt, _ := kdb.db.Prepare("select scriptAddress, purpose, keyIndex, used from keys where scriptAddress=?")
	defer stmt.Close()

	var scriptAddress string
	var purpose int
	var index int
	var used int
	err = stmt.QueryRow(hex.EncodeToString(b)).Scan(&scriptAddress, &purpose, &index, &used)
	if err != nil {
		t.Error(err)
	}
	if scriptAddress != hex.EncodeToString(b) {
		t.Errorf(`Expected %s got %s`, hex.EncodeToString(b), scriptAddress)
	}
	if purpose != 0 {
		t.Errorf(`Expected 0 got %d`, purpose)
	}
	if index != 0 {
		t.Errorf(`Expected 0 got %d`, index)
	}
	if used != 0 {
		t.Errorf(`Expected 0 got %s`, used)
	}
}

func TestImportKey(t *testing.T) {
	key, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		t.Error(err)
	}
	var b []byte
	for i := 0; i < 32; i++ {
		b = append(b, 0xff)
	}
	err = kdb.ImportKey(b, key)
	if err != nil {
		t.Error(err)
	}
	stmt, _ := kdb.db.Prepare("select scriptAddress, purpose, used, key from keys where scriptAddress=?")
	defer stmt.Close()

	var scriptAddress string
	var purpose int
	var used int
	var keyHex string
	err = stmt.QueryRow(hex.EncodeToString(b)).Scan(&scriptAddress, &purpose, &used, &keyHex)
	if err != nil {
		t.Error(err)
	}
	if scriptAddress != hex.EncodeToString(b) {
		t.Errorf(`Expected %s got %s`, hex.EncodeToString(b), scriptAddress)
	}
	if purpose != -1 {
		t.Errorf(`Expected -1 got %d`, purpose)
	}
	if used != 0 {
		t.Errorf(`Expected 0 got %s`, used)
	}
	keyBytes, err := hex.DecodeString(keyHex)
	if err != nil {
		t.Error(err)
	}
	if !bytes.Equal(key.Serialize(), keyBytes) {
		t.Errorf(`Expected %s got %s`, hex.EncodeToString(b), hex.EncodeToString(keyBytes))
	}
}

func TestPutDuplicateKey(t *testing.T) {
	b := make([]byte, 32)
	kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, 0})
	err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, 0})
	if err == nil {
		t.Error("Expected duplicate key error")
	}
}

func TestMarkKeyAsUsed(t *testing.T) {
	b := make([]byte, 33)
	err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, 0})
	if err != nil {
		t.Error(err)
	}
	err = kdb.MarkKeyAsUsed(b)
	if err != nil {
		t.Error(err)
	}
	stmt, _ := kdb.db.Prepare("select scriptAddress, purpose, keyIndex, used from keys where scriptAddress=?")
	defer stmt.Close()

	var scriptAddress string
	var purpose int
	var index int
	var used int
	err = stmt.QueryRow(hex.EncodeToString(b)).Scan(&scriptAddress, &purpose, &index, &used)
	if err != nil {
		t.Error(err)
	}
	if used != 1 {
		t.Errorf(`Expected 1 got %s`, used)
	}
}

func TestGetLastKeyIndex(t *testing.T) {
	var last []byte
	for i := 0; i < 100; i++ {
		b := make([]byte, 32)
		rand.Read(b)
		err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, i})
		if err != nil {
			t.Error(err)
		}
		last = b
	}
	idx, used, err := kdb.GetLastKeyIndex(wallet.EXTERNAL)
	if err != nil || idx != 99 || used != false {
		t.Error("Failed to fetch correct last index")
	}
	kdb.MarkKeyAsUsed(last)
	_, used, err = kdb.GetLastKeyIndex(wallet.EXTERNAL)
	if err != nil || used != true {
		t.Error("Failed to fetch correct last index")
	}
}

func TestGetPathForKey(t *testing.T) {
	b := make([]byte, 32)
	rand.Read(b)
	err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, 15})
	if err != nil {
		t.Error(err)
	}
	path, err := kdb.GetPathForKey(b)
	if err != nil {
		t.Error(err)
	}
	if path.Index != 15 || path.Purpose != wallet.EXTERNAL {
		t.Error("Returned incorrect key path")
	}
}

func TestGetKey(t *testing.T) {
	key, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		t.Error(err)
	}
	var b []byte
	for i := 0; i < 32; i++ {
		b = append(b, 0xee)
	}
	err = kdb.ImportKey(b, key)
	if err != nil {
		t.Error(err)
	}
	k, err := kdb.GetKey(b)
	if err != nil {
		t.Error(err)
	}
	if !bytes.Equal(key.Serialize(), k.Serialize()) {
		t.Error("Failed to return imported key")
	}
}

func TestKeysDB_GetImported(t *testing.T) {
	key, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		t.Error(err)
	}
	err = kdb.ImportKey([]byte("fsdfa"), key)
	if err != nil {
		t.Error(err)
	}

	keys, err := kdb.GetImported()
	if err != nil {
		t.Error(err)
	}
	if len(keys) != 1 {
		t.Error("Failed to return imported key")
	}
	if !bytes.Equal(key.Serialize(), keys[0].Serialize()) {
		t.Error("Returned incorrect key")
	}
}

func TestKeyNotFound(t *testing.T) {
	b := make([]byte, 32)
	rand.Read(b)
	_, err := kdb.GetPathForKey(b)
	if err == nil {
		t.Error("Return key when it shouldn't have")
	}
}

func TestGetUnsed(t *testing.T) {
	for i := 0; i < 100; i++ {
		b := make([]byte, 32)
		rand.Read(b)
		err := kdb.Put(b, wallet.KeyPath{wallet.INTERNAL, i})
		if err != nil {
			t.Error(err)
		}
	}
	idx, err := kdb.GetUnused(wallet.INTERNAL)
	if err != nil {
		t.Error("Failed to fetch correct unused")
	}
	if len(idx) != 100 {
		t.Error("Failed to fetch correct unused")
	}
}

func TestGetLookaheadWindows(t *testing.T) {
	for i := 0; i < 100; i++ {
		b := make([]byte, 32)
		rand.Read(b)
		err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, i})
		if err != nil {
			t.Error(err)
		}
		if i < 50 {
			kdb.MarkKeyAsUsed(b)
		}
		b = make([]byte, 32)
		rand.Read(b)
		err = kdb.Put(b, wallet.KeyPath{wallet.INTERNAL, i})
		if err != nil {
			t.Error(err)
		}
		if i < 50 {
			kdb.MarkKeyAsUsed(b)
		}
	}
	windows := kdb.GetLookaheadWindows()
	if windows[wallet.EXTERNAL] != 50 || windows[wallet.INTERNAL] != 50 {
		t.Error("Fetched incorrect lookahead windows")
	}

}
<commit_msg>Fix keys db test<commit_after>package db

import (
	"bytes"
	"crypto/rand"
	"database/sql"
	"encoding/hex"
	"github.com/OpenBazaar/wallet-interface"
	"github.com/btcsuite/btcd/btcec"
	"testing"
)

var kdb KeysDB

func init() {
	conn, _ := sql.Open("sqlite3", ":memory:")
	initDatabaseTables(conn, "")
	kdb = KeysDB{
		db: conn,
	}
}

func TestGetAll(t *testing.T) {
	for i := 0; i < 100; i++ {
		b := make([]byte, 32)
		rand.Read(b)
		err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, i})
		if err != nil {
			t.Error(err)
		}
	}
	all, err := kdb.GetAll()
	if err != nil || len(all) != 100 {
		t.Error("Failed to fetch all keys")
	}
}

func TestPutKey(t *testing.T) {
	b := make([]byte, 32)
	err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, 0})
	if err != nil {
		t.Error(err)
	}
	stmt, _ := kdb.db.Prepare("select scriptAddress, purpose, keyIndex, used from keys where scriptAddress=?")
	defer stmt.Close()

	var scriptAddress string
	var purpose int
	var index int
	var used int
	err = stmt.QueryRow(hex.EncodeToString(b)).Scan(&scriptAddress, &purpose, &index, &used)
	if err != nil {
		t.Error(err)
	}
	if scriptAddress != hex.EncodeToString(b) {
		t.Errorf(`Expected %s got %s`, hex.EncodeToString(b), scriptAddress)
	}
	if purpose != 0 {
		t.Errorf(`Expected 0 got %d`, purpose)
	}
	if index != 0 {
		t.Errorf(`Expected 0 got %d`, index)
	}
	if used != 0 {
		t.Errorf(`Expected 0 got %s`, used)
	}
}

func TestKeysDB_GetImported(t *testing.T) {
	key, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		t.Error(err)
	}
	err = kdb.ImportKey([]byte("fsdfa"), key)
	if err != nil {
		t.Error(err)
	}

	keys, err := kdb.GetImported()
	if err != nil {
		t.Error(err)
	}
	if len(keys) != 1 {
		t.Error("Failed to return imported key")
	}
	if !bytes.Equal(key.Serialize(), keys[0].Serialize()) {
		t.Error("Returned incorrect key")
	}
}

func TestImportKey(t *testing.T) {
	key, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		t.Error(err)
	}
	var b []byte
	for i := 0; i < 32; i++ {
		b = append(b, 0xff)
	}
	err = kdb.ImportKey(b, key)
	if err != nil {
		t.Error(err)
	}
	stmt, _ := kdb.db.Prepare("select scriptAddress, purpose, used, key from keys where scriptAddress=?")
	defer stmt.Close()

	var scriptAddress string
	var purpose int
	var used int
	var keyHex string
	err = stmt.QueryRow(hex.EncodeToString(b)).Scan(&scriptAddress, &purpose, &used, &keyHex)
	if err != nil {
		t.Error(err)
	}
	if scriptAddress != hex.EncodeToString(b) {
		t.Errorf(`Expected %s got %s`, hex.EncodeToString(b), scriptAddress)
	}
	if purpose != -1 {
		t.Errorf(`Expected -1 got %d`, purpose)
	}
	if used != 0 {
		t.Errorf(`Expected 0 got %s`, used)
	}
	keyBytes, err := hex.DecodeString(keyHex)
	if err != nil {
		t.Error(err)
	}
	if !bytes.Equal(key.Serialize(), keyBytes) {
		t.Errorf(`Expected %s got %s`, hex.EncodeToString(b), hex.EncodeToString(keyBytes))
	}
}

func TestPutDuplicateKey(t *testing.T) {
	b := make([]byte, 32)
	kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, 0})
	err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, 0})
	if err == nil {
		t.Error("Expected duplicate key error")
	}
}

func TestMarkKeyAsUsed(t *testing.T) {
	b := make([]byte, 33)
	err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, 0})
	if err != nil {
		t.Error(err)
	}
	err = kdb.MarkKeyAsUsed(b)
	if err != nil {
		t.Error(err)
	}
	stmt, _ := kdb.db.Prepare("select scriptAddress, purpose, keyIndex, used from keys where scriptAddress=?")
	defer stmt.Close()

	var scriptAddress string
	var purpose int
	var index int
	var used int
	err = stmt.QueryRow(hex.EncodeToString(b)).Scan(&scriptAddress, &purpose, &index, &used)
	if err != nil {
		t.Error(err)
	}
	if used != 1 {
		t.Errorf(`Expected 1 got %s`, used)
	}
}

func TestGetLastKeyIndex(t *testing.T) {
	var last []byte
	for i := 0; i < 100; i++ {
		b := make([]byte, 32)
		rand.Read(b)
		err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, i})
		if err != nil {
			t.Error(err)
		}
		last = b
	}
	idx, used, err := kdb.GetLastKeyIndex(wallet.EXTERNAL)
	if err != nil || idx != 99 || used != false {
		t.Error("Failed to fetch correct last index")
	}
	kdb.MarkKeyAsUsed(last)
	_, used, err = kdb.GetLastKeyIndex(wallet.EXTERNAL)
	if err != nil || used != true {
		t.Error("Failed to fetch correct last index")
	}
}

func TestGetPathForKey(t *testing.T) {
	b := make([]byte, 32)
	rand.Read(b)
	err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, 15})
	if err != nil {
		t.Error(err)
	}
	path, err := kdb.GetPathForKey(b)
	if err != nil {
		t.Error(err)
	}
	if path.Index != 15 || path.Purpose != wallet.EXTERNAL {
		t.Error("Returned incorrect key path")
	}
}

func TestGetKey(t *testing.T) {
	key, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		t.Error(err)
	}
	var b []byte
	for i := 0; i < 32; i++ {
		b = append(b, 0xee)
	}
	err = kdb.ImportKey(b, key)
	if err != nil {
		t.Error(err)
	}
	k, err := kdb.GetKey(b)
	if err != nil {
		t.Error(err)
	}
	if !bytes.Equal(key.Serialize(), k.Serialize()) {
		t.Error("Failed to return imported key")
	}
}

func TestKeyNotFound(t *testing.T) {
	b := make([]byte, 32)
	rand.Read(b)
	_, err := kdb.GetPathForKey(b)
	if err == nil {
		t.Error("Return key when it shouldn't have")
	}
}

func TestGetUnsed(t *testing.T) {
	for i := 0; i < 100; i++ {
		b := make([]byte, 32)
		rand.Read(b)
		err := kdb.Put(b, wallet.KeyPath{wallet.INTERNAL, i})
		if err != nil {
			t.Error(err)
		}
	}
	idx, err := kdb.GetUnused(wallet.INTERNAL)
	if err != nil {
		t.Error("Failed to fetch correct unused")
	}
	if len(idx) != 100 {
		t.Error("Failed to fetch correct unused")
	}
}

func TestGetLookaheadWindows(t *testing.T) {
	for i := 0; i < 100; i++ {
		b := make([]byte, 32)
		rand.Read(b)
		err := kdb.Put(b, wallet.KeyPath{wallet.EXTERNAL, i})
		if err != nil {
			t.Error(err)
		}
		if i < 50 {
			kdb.MarkKeyAsUsed(b)
		}
		b = make([]byte, 32)
		rand.Read(b)
		err = kdb.Put(b, wallet.KeyPath{wallet.INTERNAL, i})
		if err != nil {
			t.Error(err)
		}
		if i < 50 {
			kdb.MarkKeyAsUsed(b)
		}
	}
	windows := kdb.GetLookaheadWindows()
	if windows[wallet.EXTERNAL] != 50 || windows[wallet.INTERNAL] != 50 {
		t.Error("Fetched incorrect lookahead windows")
	}

}
<|endoftext|>


<commit_before>package engine

import (
	"fmt"
	"github.com/TakatoshiMaeda/kinu/logger"
	"gopkg.in/gographics/imagick.v2/imagick"
)

type ImageMagickEngine struct {
	ResizeEngine

	mw                *imagick.MagickWand
	opened            bool
	originalImageBlob []byte

	height, width int
}

func newImageMagickEngine(image []byte) (e *ImageMagickEngine) {
	return &ImageMagickEngine{originalImageBlob: image}
}

func (e *ImageMagickEngine) SetSizeHint(width int, height int) {
	e.height = height
	e.width = width
}

func (e *ImageMagickEngine) Open() error {
	e.mw = imagick.NewMagickWand()
	if e.height > 0 && e.width > 0 {
		e.mw.SetOption("jpeg:size", fmt.Sprintf("%dx%d", e.height, e.width))
	}
	err := e.mw.ReadImageBlob(e.originalImageBlob)
	if err != nil {
		return logger.ErrorDebug(err)
	} else {
		e.opened = true
	}
	return nil
}

func (e *ImageMagickEngine) Close() {
	if e.opened {
		e.mw.Destroy()
	}
}

func (e *ImageMagickEngine) GetImageHeight() int {
	return int(e.mw.GetImageHeight())
}

func (e *ImageMagickEngine) GetImageWidth() int {
	return int(e.mw.GetImageWidth())
}

func (e *ImageMagickEngine) Resize(width int, height int) error {
	return e.mw.ResizeImage(uint(width), uint(height), imagick.FILTER_LANCZOS, 1.0)
}

func (e *ImageMagickEngine) Crop(width int, height int, startX int, startY int) error {
	return e.mw.CropImage(uint(width), uint(height), startX, startY)
}

func (e *ImageMagickEngine) Generate() ([]byte, error) {
	return e.mw.GetImageBlob(), nil
}
<commit_msg>Change size hint variable name<commit_after>package engine

import (
	"fmt"
	"github.com/TakatoshiMaeda/kinu/logger"
	"gopkg.in/gographics/imagick.v2/imagick"
)

type ImageMagickEngine struct {
	ResizeEngine

	mw                *imagick.MagickWand
	opened            bool
	originalImageBlob []byte

	heightSizeHint, widthSizeHint int
}

func newImageMagickEngine(image []byte) (e *ImageMagickEngine) {
	return &ImageMagickEngine{originalImageBlob: image}
}

func (e *ImageMagickEngine) SetSizeHint(width int, height int) {
	e.heightSizeHint = height
	e.widthSizeHint = width
}

func (e *ImageMagickEngine) Open() error {
	e.mw = imagick.NewMagickWand()
	if e.heightSizeHint > 0 && e.widthSizeHint > 0 {
		e.mw.SetOption("jpeg:size", fmt.Sprintf("%dx%d", e.heightSizeHint, e.widthSizeHint))
	}
	err := e.mw.ReadImageBlob(e.originalImageBlob)
	if err != nil {
		return logger.ErrorDebug(err)
	} else {
		e.opened = true
	}
	return nil
}

func (e *ImageMagickEngine) Close() {
	if e.opened {
		e.mw.Destroy()
	}
}

func (e *ImageMagickEngine) GetImageHeight() int {
	return int(e.mw.GetImageHeight())
}

func (e *ImageMagickEngine) GetImageWidth() int {
	return int(e.mw.GetImageWidth())
}

func (e *ImageMagickEngine) Resize(width int, height int) error {
	return e.mw.ResizeImage(uint(width), uint(height), imagick.FILTER_LANCZOS, 1.0)
}

func (e *ImageMagickEngine) Crop(width int, height int, startX int, startY int) error {
	return e.mw.CropImage(uint(width), uint(height), startX, startY)
}

func (e *ImageMagickEngine) Generate() ([]byte, error) {
	return e.mw.GetImageBlob(), nil
}
<|endoftext|>


<commit_before>package piazza

import (
	"time"
)

type Alerter interface {
	// low-level interfaces
	PostToEvents(*Event) (*AlerterIdResponse, error)
	GetFromEvents() (*EventList, error)
	GetFromAlerts() (*AlerterIdResponse, error)
	PostToConditions(*Condition) (*AlerterIdResponse, error)
	GetFromConditions() (*ConditionList, error)
	GetFromCondition(id string) (*Condition, error)
	DeleteOfCondition(id string) error

	GetFromAdminStats() (*AlerterAdminStats, error)
	GetFromAdminSettings() (*AlerterAdminSettings, error)
	PostToAdminSettings(*AlerterAdminSettings) error
}

type AlerterIdResponse struct {
	ID string `json:"id`
}

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

const EventDataIngested = "DataIngested"
const EventDataAccessed = "DataAccessed"
const EventUSDataFound = "USDataFound"
const EventFoo = "Foo"
const EventBar = "Bar"

type EventType string

type Event struct {
	ID   string            `json:"id"`
	Type EventType         `json:"type" binding:"required"`
	Date string            `json:"date" binding:"required"`
	Data map[string]string `json:"data"` // specific to event type
}

type EventList map[string]Event

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

type Alert struct {
	ID          string `json:"id"`
	ConditionID string `json:"condition_id" binding:"required"`
	EventID     string `json:"event_id" binding:"required"`
}

type AlertList map[string]Alert

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

type Condition struct {
	ID string `json:"id"`

	Title       string    `json:"title" binding:"required"`
	Description string    `json:"description"`
	Type        EventType `json:"type" binding:"required"`
	UserID      string    `json:"user_id" binding:"required"`
	Date   string    `json:"start_date" binding:"required"`
	//ExpirationDate string `json:"expiration_date"`
	//IsEnabled      bool   `json:"is_enabled" binding:"required"`
	//HitCount int `json:"hit_count"`
}

type ConditionList map[string]Condition

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

type AlerterAdminStats struct {
	StartTime   time.Time `json:"starttime"`
	NumRequests int       `json:"num_requests"`
	NumUUIDs    int       `json:"num_uuids"`
}

type AlerterAdminSettings struct {
	Debug bool `json:"debug"`
}

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

type MockAlerter struct{}

func (*MockAlerter) PostToEvents(*Event) (*AlerterIdResponse, error) {
	return nil, nil
}

func (*MockAlerter) GetFromEvents() (*EventList, error) {
	return nil, nil
}

func (*MockAlerter) GetFromAlerts() (*AlerterIdResponse, error) {
	return nil, nil
}

func (*MockAlerter) PostToConditions(*Condition) (*AlerterIdResponse, error) {
	return nil, nil
}

func (*MockAlerter) GetFromConditions() (*ConditionList, error) {
	return nil, nil
}

func (*MockAlerter) GetFromCondition(id string) (*Condition, error) {
	return nil, nil
}

func (*MockAlerter) DeleteOfCondition(id string) error {
	return nil
}

func (*MockAlerter) GetFromAdminStats() (*AlerterAdminStats, error) {
	return &AlerterAdminStats{}, nil
}

func (*MockAlerter) GetFromAdminSettings() (*AlerterAdminSettings, error) {
	return &AlerterAdminSettings{}, nil
}

func (*MockAlerter) PostToAdminSettings(*AlerterAdminSettings) error {
	return nil
}
<commit_msg>moving types from pz-common down to a hierarchy in pz-alerter<commit_after><|endoftext|>


<commit_before>package common

import (
	"context"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"
	retry "github.com/hashicorp/packer/common"
	"github.com/hashicorp/packer/helper/multistep"
	"github.com/hashicorp/packer/packer"
)

// StepPreValidate provides an opportunity to pre-validate any configuration for
// the build before actually doing any time consuming work
//
type StepPreValidate struct {
	DestAmiName     string
	ForceDeregister bool
}

func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
	ui := state.Get("ui").(packer.Ui)

	if accessConfig, ok := state.GetOk("access_config"); ok {
		accessconf := accessConfig.(*AccessConfig)
		if !accessconf.VaultAWSEngine.Empty() {
			// loop over the authentication a few times to give vault-created creds
			// time to become eventually-consistent
			ui.Say("You're using Vault-generated AWS credentials. It may take a " +
				"few moments for them to become available on AWS. Waiting...")
			err := retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
				ec2conn, err := accessconf.NewEC2Connection()
				if err != nil {
					return true, err
				}
				_, err = listEC2Regions(ec2conn)
				if err != nil {
					if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "AuthFailure" {
						log.Printf("Waiting for Vault-generated AWS credentials" +
							" to pass authentication... trying again.")
						return false, nil
					}
				} else {
					return true, nil
				}

				return true, err
			})

			if err != nil {
				state.Put("error", fmt.Errorf("Was unable to Authenticate to AWS using Vault-"+
					"Generated Credentials within the retry timeout."))
				return multistep.ActionHalt
			}
		}

		if amiConfig, ok := state.GetOk("ami_config"); ok {
			amiconf := amiConfig.(*AMIConfig)
			if !amiconf.AMISkipRegionValidation {
				regionsToValidate := append(amiconf.AMIRegions, accessconf.RawRegion)
				err := accessconf.ValidateRegion(regionsToValidate...)
				if err != nil {
					state.Put("error", fmt.Errorf("error validating regions: %v", err))
					return multistep.ActionHalt
				}
			}
		}
	}

	if s.ForceDeregister {
		ui.Say("Force Deregister flag found, skipping prevalidating AMI Name")
		return multistep.ActionContinue
	}

	ec2conn := state.Get("ec2").(*ec2.EC2)

	ui.Say(fmt.Sprintf("Prevalidating AMI Name: %s", s.DestAmiName))
	resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
		Filters: []*ec2.Filter{{
			Name:   aws.String("name"),
			Values: []*string{aws.String(s.DestAmiName)},
		}}})

	if err != nil {
		err := fmt.Errorf("Error querying AMI: %s", err)
		state.Put("error", err)
		ui.Error(err.Error())
		return multistep.ActionHalt
	}

	if len(resp.Images) > 0 {
		err := fmt.Errorf("Error: name conflicts with an existing AMI: %s", *resp.Images[0].ImageId)
		state.Put("error", err)
		ui.Error(err.Error())
		return multistep.ActionHalt
	}

	return multistep.ActionContinue
}

func (s *StepPreValidate) Cleanup(multistep.StateBag) {}
<commit_msg>Update builder/amazon/common/step_pre_validate.go<commit_after>package common

import (
	"context"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ec2"
	retry "github.com/hashicorp/packer/common"
	"github.com/hashicorp/packer/helper/multistep"
	"github.com/hashicorp/packer/packer"
)

// StepPreValidate provides an opportunity to pre-validate any configuration for
// the build before actually doing any time consuming work
//
type StepPreValidate struct {
	DestAmiName     string
	ForceDeregister bool
}

func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
	ui := state.Get("ui").(packer.Ui)

	if accessConfig, ok := state.GetOk("access_config"); ok {
		accessconf := accessConfig.(*AccessConfig)
		if !accessconf.VaultAWSEngine.Empty() {
			// loop over the authentication a few times to give vault-created creds
			// time to become eventually-consistent
			ui.Say("You're using Vault-generated AWS credentials. It may take a " +
				"few moments for them to become available on AWS. Waiting...")
			err := retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
				ec2conn, err := accessconf.NewEC2Connection()
				if err != nil {
					return true, err
				}
				_, err = listEC2Regions(ec2conn)
				if err != nil {
					if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "AuthFailure" {
						log.Printf("Waiting for Vault-generated AWS credentials" +
							" to pass authentication... trying again.")
						return false, nil
					}
					return true, err
				} else {
					return true, nil
				}

				return true, err
			})

			if err != nil {
				state.Put("error", fmt.Errorf("Was unable to Authenticate to AWS using Vault-"+
					"Generated Credentials within the retry timeout."))
				return multistep.ActionHalt
			}
		}

		if amiConfig, ok := state.GetOk("ami_config"); ok {
			amiconf := amiConfig.(*AMIConfig)
			if !amiconf.AMISkipRegionValidation {
				regionsToValidate := append(amiconf.AMIRegions, accessconf.RawRegion)
				err := accessconf.ValidateRegion(regionsToValidate...)
				if err != nil {
					state.Put("error", fmt.Errorf("error validating regions: %v", err))
					return multistep.ActionHalt
				}
			}
		}
	}

	if s.ForceDeregister {
		ui.Say("Force Deregister flag found, skipping prevalidating AMI Name")
		return multistep.ActionContinue
	}

	ec2conn := state.Get("ec2").(*ec2.EC2)

	ui.Say(fmt.Sprintf("Prevalidating AMI Name: %s", s.DestAmiName))
	resp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
		Filters: []*ec2.Filter{{
			Name:   aws.String("name"),
			Values: []*string{aws.String(s.DestAmiName)},
		}}})

	if err != nil {
		err := fmt.Errorf("Error querying AMI: %s", err)
		state.Put("error", err)
		ui.Error(err.Error())
		return multistep.ActionHalt
	}

	if len(resp.Images) > 0 {
		err := fmt.Errorf("Error: name conflicts with an existing AMI: %s", *resp.Images[0].ImageId)
		state.Put("error", err)
		ui.Error(err.Error())
		return multistep.ActionHalt
	}

	return multistep.ActionContinue
}

func (s *StepPreValidate) Cleanup(multistep.StateBag) {}
<|endoftext|>