<commit_before>package notifiers

import (
	"encoding/json"
	"time"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/metrics"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/alerting"
	"github.com/grafana/grafana/pkg/setting"
)

// AlertStateCritical - Victorops uses "CRITICAL" string to indicate "Alerting" state
const AlertStateCritical = "CRITICAL"

func init() {
	alerting.RegisterNotifier(&alerting.NotifierPlugin{
		Type:        "victorops",
		Name:        "VictorOps",
		Description: "Sends notifications to VictorOps",
		Factory:     NewVictoropsNotifier,
		OptionsTemplate: `

VictorOps settings

        Url
        <input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="VictorOps url"></input>

    `,
	})
}

// NewVictoropsNotifier creates an instance of VictoropsNotifier that
// handles posting notifications to Victorops REST API
func NewVictoropsNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
	url := model.Settings.Get("url").MustString()
	if url == "" {
		return nil, alerting.ValidationError{Reason: "Could not find victorops url property in settings"}
	}

	return &VictoropsNotifier{
		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
		URL:          url,
		log:          log.New("alerting.notifier.victorops"),
	}, nil
}

// VictoropsNotifier defines URL property for Victorops REST API
// and handles notification process by formatting POST body according to
// Victorops specifications (http://victorops.force.com/knowledgebase/articles/Integration/Alert-Ingestion-API-Documentation/)
type VictoropsNotifier struct {
	NotifierBase
	URL string
	log log.Logger
}

// Notify sends notification to Victorops via POST to URL endpoint
func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error {
	this.log.Info("Executing victorops notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
	metrics.M_Alerting_Notification_Sent_Victorops.Inc(1)

	ruleUrl, err := evalContext.GetRuleUrl()
	if err != nil {
		this.log.Error("Failed get rule link", "error", err)
		return err
	}

	fields := make([]map[string]interface{}, 0)
	fieldLimitCount := 4
	for index, evt := range evalContext.EvalMatches {
		fields = append(fields, map[string]interface{}{
			"title": evt.Metric,
			"value": evt.Value,
			"short": true,
		})
		if index > fieldLimitCount {
			break
		}
	}

	if evalContext.Error != nil {
		fields = append(fields, map[string]interface{}{
			"title": "Error message",
			"value": evalContext.Error.Error(),
			"short": false,
		})
	}

	messageType := evalContext.Rule.State
	if evalContext.Rule.State == models.AlertStateAlerting { // translate 'Alerting' to 'CRITICAL' (Victorops analog)
		messageType = AlertStateCritical
	}

	body := map[string]interface{}{
		"message_type":     messageType,
		"entity_id":        evalContext.Rule.Name,
		"timestamp":        time.Now().Unix(),
		"state_start_time": evalContext.StartTime.Unix(),
		"state_message":    evalContext.Rule.Message + "\n" + ruleUrl,
		"monitoring_tool":  "Grafana v" + setting.BuildVersion,
	}

	data, _ := json.Marshal(&body)
	cmd := &models.SendWebhookSync{Url: this.URL, Body: string(data)}

	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
		this.log.Error("Failed to send victorops notification", "error", err, "webhook", this.Name)
		return err
	}

	return nil
}
<commit_msg>Update VictorOps notifier w/ auto resolve and image URL (#8431)<commit_after>package notifiers

import (
	"time"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/metrics"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/services/alerting"
	"github.com/grafana/grafana/pkg/setting"
)

// AlertStateCritical - Victorops uses "CRITICAL" string to indicate "Alerting" state
const AlertStateCritical = "CRITICAL"

const AlertStateRecovery = "RECOVERY"

func init() {
	alerting.RegisterNotifier(&alerting.NotifierPlugin{
		Type:        "victorops",
		Name:        "VictorOps",
		Description: "Sends notifications to VictorOps",
		Factory:     NewVictoropsNotifier,
		OptionsTemplate: `

VictorOps settings

        Url
        <input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="VictorOps url"></input>

        <gf-form-switch
           class="gf-form"
           label="Auto resolve incidents"
           label-class="width-14"
           checked="ctrl.model.settings.autoResolve"
           tooltip="Resolve incidents in VictorOps once the alert goes back to ok.">
        </gf-form-switch>

    `,
	})
}

// NewVictoropsNotifier creates an instance of VictoropsNotifier that
// handles posting notifications to Victorops REST API
func NewVictoropsNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
	autoResolve := model.Settings.Get("autoResolve").MustBool(true)
	url := model.Settings.Get("url").MustString()
	if url == "" {
		return nil, alerting.ValidationError{Reason: "Could not find victorops url property in settings"}
	}

	return &VictoropsNotifier{
		NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
		URL:          url,
		AutoResolve:  autoResolve,
		log:          log.New("alerting.notifier.victorops"),
	}, nil
}

// VictoropsNotifier defines URL property for Victorops REST API
// and handles notification process by formatting POST body according to
// Victorops specifications (http://victorops.force.com/knowledgebase/articles/Integration/Alert-Ingestion-API-Documentation/)
type VictoropsNotifier struct {
	NotifierBase
	URL         string
	AutoResolve bool
	log         log.Logger
}

// Notify sends notification to Victorops via POST to URL endpoint
func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error {
	this.log.Info("Executing victorops notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
	metrics.M_Alerting_Notification_Sent_Victorops.Inc(1)

	ruleUrl, err := evalContext.GetRuleUrl()
	if err != nil {
		this.log.Error("Failed get rule link", "error", err)
		return err
	}

	if evalContext.Rule.State == models.AlertStateOK && !this.AutoResolve {
		this.log.Info("Not alerting VictorOps", "state", evalContext.Rule.State, "auto resolve", this.AutoResolve)
		return nil
	}

	fields := make([]map[string]interface{}, 0)
	fieldLimitCount := 4
	for index, evt := range evalContext.EvalMatches {
		fields = append(fields, map[string]interface{}{
			"title": evt.Metric,
			"value": evt.Value,
			"short": true,
		})
		if index > fieldLimitCount {
			break
		}
	}

	if evalContext.Error != nil {
		fields = append(fields, map[string]interface{}{
			"title": "Error message",
			"value": evalContext.Error.Error(),
			"short": false,
		})
	}

	messageType := evalContext.Rule.State
	if evalContext.Rule.State == models.AlertStateAlerting { // translate 'Alerting' to 'CRITICAL' (Victorops analog)
		messageType = AlertStateCritical
	}

	if evalContext.Rule.State == models.AlertStateOK {
		messageType = AlertStateRecovery
	}

	bodyJSON := simplejson.New()
	bodyJSON.Set("message_type", messageType)
	bodyJSON.Set("entity_id", evalContext.Rule.Name)
	bodyJSON.Set("timestamp", time.Now().Unix())
	bodyJSON.Set("state_start_time", evalContext.StartTime.Unix())
	bodyJSON.Set("state_message", evalContext.Rule.Message)
	bodyJSON.Set("monitoring_tool", "Grafana v"+setting.BuildVersion)
	bodyJSON.Set("alert_url", ruleUrl)

	if evalContext.ImagePublicUrl != "" {
		bodyJSON.Set("image_url", evalContext.ImagePublicUrl)
	}

	data, _ := bodyJSON.MarshalJSON()
	cmd := &models.SendWebhookSync{Url: this.URL, Body: string(data)}

	if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
		this.log.Error("Failed to send Victorops notification", "error", err, "webhook", this.Name)
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package neustar

import (
	"encoding/json"
	"fmt"
	"net/http"
)

const (
	// ToolsURI is the endpoint for instant test queries
	ToolsURI = "tools/instanttest/1.0"
)

// InstantTesting
type InstantTesting struct {
	neustar *Neustar
}

// NewInstantTest returns a new InstantTesting object
func NewInstantTest(neustar *Neustar) *InstantTesting {
	return &InstantTesting{
		neustar: neustar,
	}
}

// InstantTestingJobReponse contains the response from the API
type InstantTestingData struct {
	Status       string `json:""`
	Location     string `json:"location"`
	ResponseTime int    `json:"responseTime"`
	ID           string `json:"id"`
}

// InstanceTestingResponse
type InstanceTestingResponse struct {
	Data struct {
		// A list of instant test job locations, their status and the specific location
		Items []InstantTestingData `json:"items"`

		// The ID of the instant test job
		ID string `json:"id"`
	} `json:"data"`
}

// InstantTestingByLocationResponse
type InstantTestingByLocationResponse struct {
	// The ID of the instant test job
	ID string `json:"id"`

	// The current status of the instant test job
	Status string `json:"status"`

	// Available on completion, the resultant har file for this instant
	// test job.
	HARFile string `json:"harFile"`

	// Available on completion, the base64 encoded screenshot for this
	// instant test job.
	Screenshot string `json:"screenshot"`
}

// InstantTestingCreateResponse holds the response from the API on Instant Test
// creation
type InstantTestingCreateResponse struct {
	Data struct {
		Items struct {
			ID       string `json:"id"`
			Loctions []struct {
				ID       string `json:"id"`
				Location string `json:"location"`
			} `json:"locations"`
			Created string `json:"created"`
		} `json:"items"`
	} `json:"data"`
}

// Create creates a new instant test job and return the job id of
// the new instant test job. Url is required. You may optionally
// supply a callback URL. For every stage of the instant test process,
//we will POST the current status of your instant test job
func (i *InstantTesting) Create(url, callback string) (InstantTestingCreateResponse, error) {
	var data InstantTestingCreateResponse
	return data, nil
}

// GetJob retrieves information for a specific instant test job, along
// with information from each location being tested.
func (i *InstantTesting) GetJob(instantTestID string) (InstanceTestingResponse, error) {
	var response *http.Response
	var data InstanceTestingResponse
	response, err := http.Get(
		fmt.Sprintf("%s%s/%s?apikey=%s&sig=%s", BaseURL, ToolsURI, instantTestID, i.neustar.Key, i.neustar.DigitalSignature()))
	if err != nil {
		return InstanceTestingResponse{}, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return InstanceTestingResponse{}, err
	}
	return data, nil
}

// GetJobByLocations retrieves information for a specific instant test job by location.
func (i *InstantTesting) GetJobByLocations(instantTestID, instantTestLocationID string) {}
<commit_msg>adjust comment<commit_after>package neustar

import (
	"encoding/json"
	"fmt"
	"net/http"
)

const (
	// ToolsURI is the endpoint for instant test queries
	ToolsURI = "tools/instanttest/1.0"
)

// InstantTesting
type InstantTesting struct {
	neustar *Neustar
}

// NewInstantTest returns a new InstantTesting object
func NewInstantTest(neustar *Neustar) *InstantTesting {
	return &InstantTesting{
		neustar: neustar,
	}
}

// InstantTestingJobReponse contains the response from the API
type InstantTestingData struct {
	Status       string `json:""`
	Location     string `json:"location"`
	ResponseTime int    `json:"responseTime"`
	ID           string `json:"id"`
}

// InstanceTestingResponse
type InstanceTestingResponse struct {
	Data struct {
		// A list of instant test job locations, their status and the specific location
		Items []InstantTestingData `json:"items"`

		// The ID of the instant test job
		ID string `json:"id"`
	} `json:"data"`
}

// InstantTestingByLocationResponse
type InstantTestingByLocationResponse struct {
	// The ID of the instant test job
	ID string `json:"id"`

	// The current status of the instant test job
	Status string `json:"status"`

	// Available on completion, the resultant har file for this instant
	// test job.
	HARFile string `json:"harFile"`

	// Available on completion, the base64 encoded screenshot for this
	// instant test job.
	Screenshot string `json:"screenshot"`
}

// InstantTestingCreateResponse holds the response from the API on Instant
// Test creation
type InstantTestingCreateResponse struct {
	Data struct {
		Items struct {
			ID       string `json:"id"`
			Loctions []struct {
				ID       string `json:"id"`
				Location string `json:"location"`
			} `json:"locations"`
			Created string `json:"created"`
		} `json:"items"`
	} `json:"data"`
}

// Create creates a new instant test job and return the job id of
// the new instant test job. Url is required. You may optionally
// supply a callback URL. For every stage of the instant test process,
//we will POST the current status of your instant test job
func (i *InstantTesting) Create(url, callback string) (InstantTestingCreateResponse, error) {
	var data InstantTestingCreateResponse
	return data, nil
}

// GetJob retrieves information for a specific instant test job, along
// with information from each location being tested.
func (i *InstantTesting) GetJob(instantTestID string) (InstanceTestingResponse, error) {
	var response *http.Response
	var data InstanceTestingResponse
	response, err := http.Get(
		fmt.Sprintf("%s%s/%s?apikey=%s&sig=%s", BaseURL, ToolsURI, instantTestID, i.neustar.Key, i.neustar.DigitalSignature()))
	if err != nil {
		return InstanceTestingResponse{}, err
	}
	defer response.Body.Close()
	if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
		return InstanceTestingResponse{}, err
	}
	return data, nil
}

// GetJobByLocations retrieves information for a specific instant test job by location.
func (i *InstantTesting) GetJobByLocations(instantTestID, instantTestLocationID string) {}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"sort"
	"time"
)

type HistoricalLog struct {
	Host      string
	Message   string
	Timestamp time.Time
}

type HistoricalLogs []HistoricalLog

// Len is part of sort.Interface.
func (hl HistoricalLogs) Len() int {
	return len(hl)
}

// Swap is part of sort.Interface.
func (hl HistoricalLogs) Swap(i, j int) {
	hl[i], hl[j] = hl[j], hl[i]
}

// Less is part of sort.Interface. We use count as the value to sort by
func (hl HistoricalLogs) Less(i, j int) bool {
	return hl[i].Timestamp.Before(hl[j].Timestamp)
}

func (api *Api) GetHistoricalLogs(deploymentId string) (historicalLogs HistoricalLogs, err error) {
	body, err := api.restGet(api.apiUrl("/deployments/"+deploymentId+"/historical_logs?size=200&sort=desc&grep_o=connection&grep=query"))
	if err != nil {
		return nil, err
	}
	result := make(map[string]interface{})
	err = json.Unmarshal(body, &result)
	if err != nil {
		return nil, err
	}
	for host, logs := range result {
		for _, log := range logs.(map[string]interface{})["logs"].([]interface{}) {
			ts := log.(map[string]interface{})["ts"].(string)
			timestamp, _ := time.Parse("2006-01-02T15:04:05Z", ts)
			historicalLogs = append(historicalLogs, HistoricalLog{Host: host, Message: log.(map[string]interface{})["message"].(string), Timestamp: timestamp})
		}
	}
	sort.Sort(historicalLogs)
	return historicalLogs, err
}
<commit_msg>Do not filter out connections on logs<commit_after>package main

import (
	"encoding/json"
	"sort"
	"time"
)

type HistoricalLog struct {
	Host      string
	Message   string
	Timestamp time.Time
}

type HistoricalLogs []HistoricalLog

// Len is part of sort.Interface.
func (hl HistoricalLogs) Len() int {
	return len(hl)
}

// Swap is part of sort.Interface.
func (hl HistoricalLogs) Swap(i, j int) {
	hl[i], hl[j] = hl[j], hl[i]
}

// Less is part of sort.Interface. We use count as the value to sort by
func (hl HistoricalLogs) Less(i, j int) bool {
	return hl[i].Timestamp.Before(hl[j].Timestamp)
}

func (api *Api) GetHistoricalLogs(deploymentId string) (historicalLogs HistoricalLogs, err error) {
	body, err := api.restGet(api.apiUrl("/deployments/"+deploymentId+"/historical_logs?size=200&sort=desc"))
	if err != nil {
		return nil, err
	}
	result := make(map[string]interface{})
	err = json.Unmarshal(body, &result)
	if err != nil {
		return nil, err
	}
	for host, logs := range result {
		for _, log := range logs.(map[string]interface{})["logs"].([]interface{}) {
			ts := log.(map[string]interface{})["ts"].(string)
			timestamp, _ := time.Parse("2006-01-02T15:04:05Z", ts)
			historicalLogs = append(historicalLogs, HistoricalLog{Host: host, Message: log.(map[string]interface{})["message"].(string), Timestamp: timestamp})
		}
	}
	sort.Sort(historicalLogs)
	return historicalLogs, err
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold 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 generatepipeline

import (
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestGenerateBuildTasks(t *testing.T) {
	var tests = []struct {
		description string
		configFiles []*ConfigFile
		shouldErr   bool
		runCtx      *runcontext.RunContext
	}{
		{
			description: "successfully generate build tasks",
			configFiles: []*ConfigFile{
				{
					Path: "test1",
					Profile: &latest.Profile{
						Pipeline: latest.Pipeline{
							Build: latest.BuildConfig{
								Artifacts: []*latest.Artifact{
									{
										ImageName: "testArtifact1",
									},
								},
							},
						},
					},
				},
				{
					Path: "test2",
					Profile: &latest.Profile{
						Pipeline: latest.Pipeline{
							Build: latest.BuildConfig{
								Artifacts: []*latest.Artifact{
									{
										ImageName: "testArtifact2",
									},
								},
							},
						},
					},
				},
			},
			runCtx: &runcontext.RunContext{
				Opts: config.SkaffoldOptions{
					Namespace: "test-ns",
				},
			},
			shouldErr: false,
		},
	}

	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			_, err := generateBuildTasks(test.runCtx, test.configFiles)
			t.CheckError(test.shouldErr, err)
		})
	}
}

func TestGenerateBuildTask(t *testing.T) {
	var tests = []struct {
		description string
		buildConfig latest.BuildConfig
		shouldErr   bool
	}{
		{
			description: "successfully generate build task",
			buildConfig: latest.BuildConfig{
				Artifacts: []*latest.Artifact{
					{
						ImageName: "testArtifact",
					},
				},
			},
			shouldErr: false,
		},
		{
			description: "fail generating build task",
			buildConfig: latest.BuildConfig{
				Artifacts: []*latest.Artifact{},
			},
			shouldErr: true,
		},
	}

	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			configFile := &ConfigFile{
				Path: "test",
				Profile: &latest.Profile{
					Pipeline: latest.Pipeline{
						Build: test.buildConfig,
					},
				},
			}
			_, err := generateBuildTask(configFile)
			t.CheckError(test.shouldErr, err)
		})
	}
}

func TestGenerateDeployTasks(t *testing.T) {
	var tests = []struct {
		description string
		configFiles []*ConfigFile
		shouldErr   bool
		runCtx      *runcontext.RunContext
	}{
		{
			description: "successfully generate deploy tasks",
			configFiles: []*ConfigFile{
				{
					Path: "test1",
					Config: &latest.SkaffoldConfig{
						Pipeline: latest.Pipeline{
							Deploy: latest.DeployConfig{
								DeployType: latest.DeployType{
									HelmDeploy: &latest.HelmDeploy{},
								},
							},
						},
					},
				},
				{
					Path: "test2",
					Config: &latest.SkaffoldConfig{
						Pipeline: latest.Pipeline{
							Deploy: latest.DeployConfig{
								DeployType: latest.DeployType{
									HelmDeploy: &latest.HelmDeploy{},
								},
							},
						},
					},
				},
			},
			runCtx: &runcontext.RunContext{
				Opts: config.SkaffoldOptions{
					Namespace: "test-ns",
				},
			},
			shouldErr: false,
		},
	}

	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			_, err := generateDeployTasks(test.runCtx, test.configFiles)
			t.CheckError(test.shouldErr, err)
		})
	}
}

func TestGenerateDeployTask(t *testing.T) {
	var tests = []struct {
		description  string
		deployConfig latest.DeployConfig
		shouldErr    bool
	}{
		{
			description: "successfully generate deploy task",
			deployConfig: latest.DeployConfig{
				DeployType: latest.DeployType{
					HelmDeploy: &latest.HelmDeploy{},
				},
			},
			shouldErr: false,
		},
		{
			description: "fail generating deploy task",
			deployConfig: latest.DeployConfig{
				DeployType: latest.DeployType{
					HelmDeploy:      nil,
					KubectlDeploy:   nil,
					KustomizeDeploy: nil,
				},
			},
			shouldErr: true,
		},
	}

	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			configFile := &ConfigFile{
				Path: "test",
				Config: &latest.SkaffoldConfig{
					Pipeline: latest.Pipeline{
						Deploy: test.deployConfig,
					},
				},
			}

			_, err := generateDeployTask(configFile)
			t.CheckError(test.shouldErr, err)
		})
	}
}
<commit_msg>improve unit testing for task generation<commit_after>/*
Copyright 2019 The Skaffold 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 generatepipeline

import (
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/testutil"

	tekton "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestGenerateBuildTasks(t *testing.T) {
	var tests = []struct {
		description   string
		configFiles   []*ConfigFile
		shouldErr     bool
		namespace     string
		expectedTasks []*tekton.Task
	}{
		{
			description: "successfully generate build tasks",
			configFiles: []*ConfigFile{
				{
					Path: "test1",
					Profile: &latest.Profile{
						Pipeline: latest.Pipeline{
							Build: latest.BuildConfig{
								Artifacts: []*latest.Artifact{
									{
										ImageName: "testArtifact1",
									},
								},
							},
						},
					},
				},
				{
					Path: "test2",
					Profile: &latest.Profile{
						Pipeline: latest.Pipeline{
							Build: latest.BuildConfig{
								Artifacts: []*latest.Artifact{
									{
										ImageName: "testArtifact2",
									},
								},
							},
						},
					},
				},
			},
			namespace: "",
			shouldErr: false,
			expectedTasks: []*tekton.Task{
				{
					TypeMeta:   metav1.TypeMeta{Kind: "Task", APIVersion: "tekton.dev/v1alpha1"},
					ObjectMeta: metav1.ObjectMeta{Name: "skaffold-build"},
					Spec: tekton.TaskSpec{
						Inputs:  &tekton.Inputs{Resources: []tekton.TaskResource{{Name: "source", Type: "git"}}},
						Outputs: &tekton.Outputs{Resources: []tekton.TaskResource{{Name: "source", Type: "git"}}},
						Steps: []corev1.Container{
							{
								Name:       "run-build",
								Image:      "gcr.io/k8s-skaffold/skaffold:",
								Command:    []string{"skaffold", "build"},
								Args:       []string{"--filename", "test1", "--profile", "oncluster", "--file-output", "build.out"},
								WorkingDir: "/workspace/source",
							},
						},
					},
				},
				{
					TypeMeta:   metav1.TypeMeta{Kind: "Task", APIVersion: "tekton.dev/v1alpha1"},
					ObjectMeta: metav1.ObjectMeta{Name: "skaffold-build"},
					Spec: tekton.TaskSpec{
						Inputs:  &tekton.Inputs{Resources: []tekton.TaskResource{{Name: "source", Type: "git"}}},
						Outputs: &tekton.Outputs{Resources: []tekton.TaskResource{{Name: "source", Type: "git"}}},
						Steps: []corev1.Container{
							{
								Name:       "run-build",
								Image:      "gcr.io/k8s-skaffold/skaffold:",
								Command:    []string{"skaffold", "build"},
								Args:       []string{"--filename", "test2", "--profile", "oncluster", "--file-output", "build.out"},
								WorkingDir: "/workspace/source",
							},
						},
					},
				},
			},
		},
		{
			description: "build task with namespace",
			configFiles: []*ConfigFile{
				{
					Path: "test1",
					Profile: &latest.Profile{
						Pipeline: latest.Pipeline{
							Build: latest.BuildConfig{
								Artifacts: []*latest.Artifact{
									{
										ImageName: "testArtifact1",
									},
								},
							},
						},
					},
				},
			},
			namespace: "test-ns",
			shouldErr: false,
			expectedTasks: []*tekton.Task{
				{
					TypeMeta:   metav1.TypeMeta{Kind: "Task", APIVersion: "tekton.dev/v1alpha1"},
					ObjectMeta: metav1.ObjectMeta{Name: "skaffold-build"},
					Spec: tekton.TaskSpec{
						Inputs:  &tekton.Inputs{Resources: []tekton.TaskResource{{Name: "source", Type: "git"}}},
						Outputs: &tekton.Outputs{Resources: []tekton.TaskResource{{Name: "source", Type: "git"}}},
						Steps: []corev1.Container{
							{
								Name:    "run-build",
								Image:   "gcr.io/k8s-skaffold/skaffold:",
								Command: []string{"skaffold", "build"},
								Args: []string{
									"--filename",
									"test1",
									"--profile",
									"oncluster",
									"--file-output",
									"build.out",
									"--namespace",
									"test-ns",
								},
								WorkingDir: "/workspace/source",
							},
						},
					},
				},
			},
		},
	}

	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			got, err := generateBuildTasks(test.namespace, test.configFiles)
			t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expectedTasks, got)
		})
	}
}

func TestGenerateBuildTask(t *testing.T) {
	var tests = []struct {
		description string
		buildConfig latest.BuildConfig
		shouldErr   bool
	}{
		{
			description: "successfully generate build task",
			buildConfig: latest.BuildConfig{
				Artifacts: []*latest.Artifact{
					{
						ImageName: "testArtifact",
					},
				},
			},
			shouldErr: false,
		},
		{
			description: "fail generating build task",
			buildConfig: latest.BuildConfig{
				Artifacts: []*latest.Artifact{},
			},
			shouldErr: true,
		},
	}

	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			configFile := &ConfigFile{
				Path: "test",
				Profile: &latest.Profile{
					Pipeline: latest.Pipeline{
						Build: test.buildConfig,
					},
				},
			}
			_, err := generateBuildTask(configFile)
			t.CheckError(test.shouldErr, err)
		})
	}
}

func TestGenerateDeployTasks(t *testing.T) {
	var tests = []struct {
		description   string
		configFiles   []*ConfigFile
		shouldErr     bool
		namespace     string
		expectedTasks []*tekton.Task
	}{
		{
			description: "successfully generate deploy tasks",
			configFiles: []*ConfigFile{
				{
					Path: "test1",
					Config: &latest.SkaffoldConfig{
						Pipeline: latest.Pipeline{
							Deploy: latest.DeployConfig{
								DeployType: latest.DeployType{
									HelmDeploy: &latest.HelmDeploy{},
								},
							},
						},
					},
				},
				{
					Path: "test2",
					Config: &latest.SkaffoldConfig{
						Pipeline: latest.Pipeline{
							Deploy: latest.DeployConfig{
								DeployType: latest.DeployType{
									HelmDeploy: &latest.HelmDeploy{},
								},
							},
						},
					},
				},
			},
			namespace: "",
			shouldErr: false,
			expectedTasks: []*tekton.Task{
				{
					TypeMeta:   metav1.TypeMeta{Kind: "Task", APIVersion: "tekton.dev/v1alpha1"},
					ObjectMeta: metav1.ObjectMeta{Name: "skaffold-deploy"},
					Spec: tekton.TaskSpec{
						Inputs: &tekton.Inputs{Resources: []tekton.TaskResource{{Name: "source", Type: "git"}}},
						Steps: []corev1.Container{
							{
								Name:       "run-deploy",
								Image:      "gcr.io/k8s-skaffold/skaffold:",
								Command:    []string{"skaffold", "deploy"},
								Args:       []string{"--filename", "test1", "--build-artifacts", "build.out"},
								WorkingDir: "/workspace/source",
							},
						},
					},
				},
				{
					TypeMeta:   metav1.TypeMeta{Kind: "Task", APIVersion: "tekton.dev/v1alpha1"},
					ObjectMeta: metav1.ObjectMeta{Name: "skaffold-deploy"},
					Spec: tekton.TaskSpec{
						Inputs: &tekton.Inputs{Resources: []tekton.TaskResource{{Name: "source", Type: "git"}}},
						Steps: []corev1.Container{
							{
								Name:       "run-deploy",
								Image:      "gcr.io/k8s-skaffold/skaffold:",
								Command:    []string{"skaffold", "deploy"},
								Args:       []string{"--filename", "test2", "--build-artifacts", "build.out"},
								WorkingDir: "/workspace/source",
							},
						},
					},
				},
			},
		},
		{
			description: "deploy task with namespace",
			configFiles: []*ConfigFile{
				{
					Path: "test1",
					Config: &latest.SkaffoldConfig{
						Pipeline: latest.Pipeline{
							Deploy: latest.DeployConfig{
								DeployType: latest.DeployType{
									HelmDeploy: &latest.HelmDeploy{},
								},
							},
						},
					},
				},
			},
			namespace: "test-ns",
			shouldErr: false,
			expectedTasks: []*tekton.Task{
				{
					TypeMeta:   metav1.TypeMeta{Kind: "Task", APIVersion: "tekton.dev/v1alpha1"},
					ObjectMeta: metav1.ObjectMeta{Name: "skaffold-deploy"},
					Spec: tekton.TaskSpec{
						Inputs: &tekton.Inputs{Resources: []tekton.TaskResource{{Name: "source", Type: "git"}}},
						Steps: []corev1.Container{
							{
								Name:    "run-deploy",
								Image:   "gcr.io/k8s-skaffold/skaffold:",
								Command: []string{"skaffold", "deploy"},
								Args: []string{
									"--filename",
									"test1",
									"--build-artifacts",
									"build.out",
									"--namespace",
									"test-ns",
								},
								WorkingDir: "/workspace/source",
							},
						},
					},
				},
			},
		},
	}

	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			got, err := generateDeployTasks(test.namespace, test.configFiles)
			t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expectedTasks, got)
		})
	}
}

func TestGenerateDeployTask(t *testing.T) {
	var tests = []struct {
		description  string
		deployConfig latest.DeployConfig
		shouldErr    bool
	}{
		{
			description: "successfully generate deploy task",
			deployConfig: latest.DeployConfig{
				DeployType: latest.DeployType{
					HelmDeploy: &latest.HelmDeploy{},
				},
			},
			shouldErr: false,
		},
		{
			description: "fail generating deploy task",
			deployConfig: latest.DeployConfig{
				DeployType: latest.DeployType{
					HelmDeploy:      nil,
					KubectlDeploy:   nil,
					KustomizeDeploy: nil,
				},
			},
			shouldErr: true,
		},
	}

	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			configFile := &ConfigFile{
				Path: "test",
				Config: &latest.SkaffoldConfig{
					Pipeline: latest.Pipeline{
						Deploy: test.deployConfig,
					},
				},
			}

			_, err := generateDeployTask(configFile)
			t.CheckError(test.shouldErr, err)
		})
	}
}
<|endoftext|>


<commit_before>package logstash

import (
	"encoding/json"
	"errors"
	"log"
	"net"
	"os"

	"github.com/gliderlabs/logspout/router"
)

func init() {
	router.AdapterFactories.Register(NewLogstashAdapter, "logstash")
}

// LogstashAdapter is an adapter that streams UDP JSON to Logstash.
type LogstashAdapter struct {
	conn  net.Conn
	route *router.Route
}

func getopt(name, dfault string) string {
	value := os.Getenv(name)
	if value == "" {
		value = dfault
	}
	return value
}

// NewLogstashAdapter creates a LogstashAdapter with UDP as the default transport.
func NewLogstashAdapter(route *router.Route) (router.LogAdapter, error) {
	transport, found := router.AdapterTransports.Lookup(route.AdapterTransport("udp"))
	if !found {
		return nil, errors.New("unable to find adapter: " + route.Adapter)
	}

	conn, err := transport.Dial(route.Address, route.Options)
	if err != nil {
		return nil, err
	}

	return &LogstashAdapter{
		route: route,
		conn:  conn,
	}, nil
}

// Stream implements the router.LogAdapter interface.
func (a *LogstashAdapter) Stream(logstream chan *router.Message) {

	opt_string := getopt("OPTIONS", "")
	var options *map[string]string

	if opt_string != "" {
		b := []byte(opt_string)

		if err := json.Unmarshal(b, &options); err != nil {
			return err
		}
	}

	for m := range logstream {
		msg := LogstashMessage{
			Message:  m.Data,
			Name:     m.Container.Name,
			ID:       m.Container.ID,
			Image:    m.Container.Config.Image,
			Hostname: m.Container.Config.Hostname,
			Args:     m.Container.Args,
			Options:  *options,
		}
		js, err := json.Marshal(msg)
		if err != nil {
			log.Println("logstash:", err)
			continue
		}
		_, err = a.conn.Write(js)
		if err != nil {
			log.Println("logstash:", err)
			continue
		}
	}
}

// LogstashMessage is a simple JSON input to Logstash.
type LogstashMessage struct {
	Message  string            `json:"message"`
	Name     string            `json:"docker.name"`
	ID       string            `json:"docker.id"`
	Image    string            `json:"docker.image"`
	Hostname string            `json:"docker.hostname"`
	Args     []string          `json:"docker.args,omitempty"`
	Options  map[string]string `json:"options,omitempty"`
}
<commit_msg>clearing out options if it can't be processed<commit_after>package logstash

import (
	"encoding/json"
	"errors"
	"log"
	"net"
	"os"

	"github.com/gliderlabs/logspout/router"
)

func init() {
	router.AdapterFactories.Register(NewLogstashAdapter, "logstash")
}

// LogstashAdapter is an adapter that streams UDP JSON to Logstash.
type LogstashAdapter struct {
	conn  net.Conn
	route *router.Route
}

func getopt(name, dfault string) string {
	value := os.Getenv(name)
	if value == "" {
		value = dfault
	}
	return value
}

// NewLogstashAdapter creates a LogstashAdapter with UDP as the default transport.
func NewLogstashAdapter(route *router.Route) (router.LogAdapter, error) {
	transport, found := router.AdapterTransports.Lookup(route.AdapterTransport("udp"))
	if !found {
		return nil, errors.New("unable to find adapter: " + route.Adapter)
	}

	conn, err := transport.Dial(route.Address, route.Options)
	if err != nil {
		return nil, err
	}

	return &LogstashAdapter{
		route: route,
		conn:  conn,
	}, nil
}

// Stream implements the router.LogAdapter interface.
func (a *LogstashAdapter) Stream(logstream chan *router.Message) {

	opt_string := getopt("OPTIONS", "")
	var options *map[string]string

	if opt_string != "" {
		b := []byte(opt_string)

		if err := json.Unmarshal(b, &options); err != nil {
			options := nil
		}
	}

	for m := range logstream {
		msg := LogstashMessage{
			Message:  m.Data,
			Name:     m.Container.Name,
			ID:       m.Container.ID,
			Image:    m.Container.Config.Image,
			Hostname: m.Container.Config.Hostname,
			Args:     m.Container.Args,
			Options:  *options,
		}
		js, err := json.Marshal(msg)
		if err != nil {
			log.Println("logstash:", err)
			continue
		}
		_, err = a.conn.Write(js)
		if err != nil {
			log.Println("logstash:", err)
			continue
		}
	}
}

// LogstashMessage is a simple JSON input to Logstash.
type LogstashMessage struct {
	Message  string            `json:"message"`
	Name     string            `json:"docker.name"`
	ID       string            `json:"docker.id"`
	Image    string            `json:"docker.image"`
	Hostname string            `json:"docker.hostname"`
	Args     []string          `json:"docker.args,omitempty"`
	Options  map[string]string `json:"options,omitempty"`
}
<|endoftext|>


<commit_before>package apply

import (
	"fmt"
	"github.com/Aptomi/aptomi/pkg/slinga/engine/actual"
	"github.com/Aptomi/aptomi/pkg/slinga/engine/diff"
	"github.com/Aptomi/aptomi/pkg/slinga/engine/resolve"
	"github.com/Aptomi/aptomi/pkg/slinga/event"
	"github.com/Aptomi/aptomi/pkg/slinga/external"
	"github.com/Aptomi/aptomi/pkg/slinga/lang"
	"github.com/Aptomi/aptomi/pkg/slinga/lang/builder"
	"github.com/Aptomi/aptomi/pkg/slinga/plugin"
	"github.com/Aptomi/aptomi/pkg/slinga/util"
	"github.com/stretchr/testify/assert"
	"strings"
	"testing"
	"time"
)

const (
	ResSuccess = iota
	ResError   = iota
)

func TestApplyComponentCreateSuccess(t *testing.T) {
	// resolve empty policy
	bActual := builder.NewPolicyBuilder()
	actualState := resolvePolicy(t, bActual)

	// resolve full policy
	bDesired := makePolicyBuilder()
	desiredState := resolvePolicy(t, bDesired)

	// process all actions
	actions := diff.NewPolicyResolutionDiff(desiredState, actualState, 0).Actions

	applier := NewEngineApply(
		bDesired.Policy(),
		desiredState,
		bActual.Policy(),
		actualState,
		actual.NewNoOpActionStateUpdater(),
		bDesired.External(),
		MockPluginFailOnComponent(),
		actions,
	)

	// check actual state
	assert.Equal(t, 0, len(actualState.ComponentInstanceMap), "Actual state should be empty")

	// check that policy apply finished with expected results
	actualState = applyAndCheck(t, applier, ResSuccess, 0, "Successfully resolved")

	// check that actual state got updated
	assert.Equal(t, 2, len(actualState.ComponentInstanceMap), "Actual state should not be empty after apply()")
}

func TestApplyComponentCreateFailure(t *testing.T) {
	// resolve empty policy
	bActual := builder.NewPolicyBuilder()
	actualState := resolvePolicy(t, bActual)

	// resolve full policy
	bDesired := makePolicyBuilder()
	desiredState := resolvePolicy(t, bDesired)

	// process all actions (and make component fail deployment)
	actions := diff.NewPolicyResolutionDiff(desiredState, actualState, 0).Actions
	applier := NewEngineApply(
		bDesired.Policy(),
		desiredState,
		bActual.Policy(),
		actualState,
		actual.NewNoOpActionStateUpdater(),
		bDesired.External(),
		MockPluginFailOnComponent(bDesired.Policy().GetObjectsByKind(lang.ServiceObject.Kind)[0].(*lang.Service).Components[0].Name),
		actions,
	)

	// check actual state
	assert.Equal(t, 0, len(actualState.ComponentInstanceMap), "Actual state should be empty")

	// check that policy apply finished with expected results
	actualState = applyAndCheck(t, applier, ResError, 1, "Apply failed for component")

	// check that actual state got updated (service component exists, but no child components got deployed)
	assert.Equal(t, 1, len(actualState.ComponentInstanceMap), "Actual state should not be empty after apply()")
}

func TestDiffHasUpdatedComponentsAndCheckTimes(t *testing.T) {
	/*
		Step 1: actual = empty, desired = test policy, check = kafka update/create times
	*/

	// Create initial empty resolution data (do not resolve any dependencies)
	bActual := builder.NewPolicyBuilder()
	actualState := resolvePolicy(t, bActual)

	// Resolve all dependencies in policy
	bDesired := makePolicyBuilder()
	desiredState := resolvePolicy(t, bDesired)

	// Apply to update component times in actual state
	applier := NewEngineApply(
		bDesired.Policy(),
		desiredState,
		bActual.Policy(),
		actualState,
		actual.NewNoOpActionStateUpdater(),
		bDesired.External(),
		MockPluginFailOnComponent(),
		diff.NewPolicyResolutionDiff(desiredState, actualState, 0).Actions,
	)

	// Check that policy apply finished with expected results
	updatedActualState := applyAndCheck(t, applier, ResSuccess, 0, "Successfully resolved")

	// Get key to a component
	cluster := bDesired.Policy().GetObjectsByKind(lang.ClusterObject.Kind)[0].(*lang.Cluster)
	contract := bDesired.Policy().GetObjectsByKind(lang.ContractObject.Kind)[0].(*lang.Contract)
	service := bDesired.Policy().GetObjectsByKind(lang.ServiceObject.Kind)[0].(*lang.Service)
	key := resolve.NewComponentInstanceKey(cluster, contract, contract.Contexts[0], nil, service, service.Components[0])
	keyService := key.GetParentServiceKey()

	// Check creation/update times
	times1 := getTimes(t, key.GetKey(), updatedActualState)
	assert.WithinDuration(t, time.Now(), times1.created, time.Second, "Creation time should be initialized correctly")
	assert.Equal(t, times1.updated, times1.updated, "Update time should be equal to creation time")

	actualState = updatedActualState
	bActual = bDesired

	/*
		Step 2: desired = add a dependency, check = component update/create times remained the same in actual state
	*/

	// Sleep a little bit to introduce time delay
	time.Sleep(25 * time.Millisecond)

	// Add another dependency, resolve, calculate difference against prev resolution data
	bDesiredNext := makePolicyBuilder()
	dependencyNew := bDesiredNext.AddDependency(bDesiredNext.AddUser(), contract)
	dependencyNew.Labels["param"] = "value1"

	desiredStateNext := resolvePolicy(t, bDesiredNext)
	assert.Contains(t, desiredStateNext.DependencyInstanceMap, dependencyNew.GetKey(), "New dependency should also be resolved")

	// Apply to update component times in actual state
	applier = NewEngineApply(
		bDesiredNext.Policy(),
		desiredStateNext,
		bActual.Policy(),
		actualState,
		actual.NewNoOpActionStateUpdater(),
		bDesiredNext.External(),
		MockPluginFailOnComponent(),
		diff.NewPolicyResolutionDiff(desiredStateNext, actualState, 0).Actions,
	)

	// Check that policy apply finished with expected results
	updatedActualState = applyAndCheck(t, applier, ResSuccess, 0, "Successfully resolved")

	// Check creation/update times
	times2 := getTimes(t, key.GetKey(), updatedActualState)
	assert.Equal(t, times1.created, times2.created, "Creation time should be carried over to remain the same")
	assert.Equal(t, times1.updated, times2.updated, "Update time should be carried over to remain the same")

	actualState = updatedActualState
	bActual = bDesiredNext

	/*
		Step 3: desired = update user label, check = component update time changed
	*/
	componentTimes := getTimes(t, key.GetKey(), actualState)
	serviceTimes := getTimes(t, keyService.GetKey(), actualState)

	// Sleep a little bit to introduce time delay
	time.Sleep(25 * time.Millisecond)

	// Update labels, re-evaluate and see that component instance has changed
	for _, dependency := range bDesiredNext.Policy().GetObjectsByKind(lang.DependencyObject.Kind) {
		dependency.(*lang.Dependency).Labels["param"] = "value2"
	}
	desiredStateAfterUpdate := resolvePolicy(t, bDesiredNext)

	// Apply to update component times in actual state
	applier = NewEngineApply(
		bDesiredNext.Policy(),
		desiredStateAfterUpdate,
		bActual.Policy(),
		actualState,
		actual.NewNoOpActionStateUpdater(),
		bDesiredNext.External(),
		MockPluginFailOnComponent(),
		diff.NewPolicyResolutionDiff(desiredStateAfterUpdate, actualState, 0).Actions,
	)

	// Check that policy apply finished with expected results
	updatedActualState = applyAndCheck(t, applier, ResSuccess, 0, "Successfully resolved")

	// Check creation/update times for component
	componentTimesUpdated := getTimes(t, key.GetKey(), updatedActualState)
	assert.Equal(t, componentTimes.created, componentTimesUpdated.created, "Creation time for component should be carried over to remain the same")
	assert.True(t, componentTimesUpdated.updated.After(componentTimes.updated), "Update time for component should be changed")

	// Check creation/update times for service
	serviceTimesUpdated := getTimes(t, keyService.GetKey(), updatedActualState)
	assert.Equal(t, serviceTimes.created, serviceTimesUpdated.created, "Creation time for parent service should be carried over to remain the same")
	assert.True(t, serviceTimesUpdated.updated.After(serviceTimes.updated), "Update time for parent service should be changed")
}

/*
	Helpers
*/

func makePolicyBuilder() *builder.PolicyBuilder {
	b := builder.NewPolicyBuilder()

	// create a service
	service := b.AddService(b.AddUser())
	b.AddServiceComponent(service,
		b.CodeComponent(
			util.NestedParameterMap{
				"param":   "{{ .Labels.param }}",
				"cluster": "{{ .Labels.cluster }}",
			},
			nil,
		),
	)
	contract := b.AddContract(service, b.CriteriaTrue())

	// add rules to allow all dependencies
	clusterObj := b.AddCluster()
	b.AddRule(b.CriteriaTrue(), b.RuleActions(lang.Allow, lang.Allow, lang.NewLabelOperationsSetSingleLabel(lang.LabelCluster, clusterObj.Name)))

	// add dependency
	dependency := b.AddDependency(b.AddUser(), contract)
	dependency.Labels["param"] = "value1"

	return b
}

func resolvePolicy(t *testing.T, b *builder.PolicyBuilder) *resolve.PolicyResolution {
	t.Helper()
	resolver := resolve.NewPolicyResolver(b.Policy(), b.External())
	result, eventLog, err := resolver.ResolveAllDependencies()
	if !assert.NoError(t, err, "Policy should be resolved without errors") {
		hook := &event.HookStdout{}
		eventLog.Save(hook)
		t.FailNow()
	}

	return result
}

func applyAndCheck(t *testing.T, apply *EngineApply, expectedResult int, errorCnt int, expectedMessage string) *resolve.PolicyResolution {
	t.Helper()
	actualState, eventLog, err := apply.Apply()

	if !assert.Equal(t, expectedResult != ResError, err == nil, "Apply status (success vs. error)") {
		// print log into stdout and exit
		hook := &event.HookStdout{}
		eventLog.Save(hook)
		t.FailNow()
	}

	if expectedResult == ResError {
		// check for error messages
		verifier := event.NewUnitTestLogVerifier(expectedMessage, expectedResult == ResError)
		eventLog.Save(verifier)
		if !assert.Equal(t, errorCnt, verifier.MatchedErrorsCount(), "Apply event log should have correct number of messages containing words: "+expectedMessage) {
			hook := &event.HookStdout{}
			eventLog.Save(hook)
			t.FailNow()
		}
	}
	return actualState
}

type componentTimes struct {
	created time.Time
	updated time.Time
}

func getTimes(t *testing.T, key string, u2 *resolve.PolicyResolution) componentTimes {
	t.Helper()
	return componentTimes{
		created: getInstanceInternal(t, key, u2).CreatedOn,
		updated: getInstanceInternal(t, key, u2).UpdatedOn,
	}
}

func getInstanceInternal(t *testing.T, key string, resolution *resolve.PolicyResolution) *resolve.ComponentInstance {
	t.Helper()
	instance, ok := resolution.ComponentInstanceMap[key]
	if !assert.True(t, ok, "Component instance exists in resolution data: "+key) {
		t.FailNow()
	}
	return instance
}

func MockPluginFailOnComponent(failComponents ...string) plugin.Registry {
	return &testRegistry{&testPlugin{failComponents}}
}

type testRegistry struct {
	*testPlugin
}

func (reg *testRegistry) GetDeployPlugin(codeType string) (plugin.DeployPlugin, error) {
	return reg.testPlugin, nil
}

func (reg *testRegistry) GetClustersPostProcessingPlugins() []plugin.ClustersPostProcessPlugin {
	return []plugin.ClustersPostProcessPlugin{reg.testPlugin}
}

type testPlugin struct {
	failComponents []string
}

func (p *testPlugin) Cleanup() error {
	return nil
}

func (p *testPlugin) GetSupportedCodeTypes() []string {
	return []string{}
}

func (p *testPlugin) Create(cluster *lang.Cluster, deployName string, params util.NestedParameterMap, eventLog *event.Log) error {
	eventLog.WithFields(event.Fields{}).Infof("[+] %s", deployName)
	for _, s := range p.failComponents {
		if strings.Contains(deployName, s) {
			return fmt.Errorf("Apply failed for component: %s", deployName)
		}
	}
	return nil
}

func (p *testPlugin) Update(cluster *lang.Cluster, deployName string, params util.NestedParameterMap, eventLog *event.Log) error {
	eventLog.WithFields(event.Fields{}).Infof("[*] %s", deployName)
	for _, s := range p.failComponents {
		if strings.Contains(deployName, s) {
			return fmt.Errorf("Update failed for component: %s", deployName)
		}
	}
	return nil
}

func (p *testPlugin) Destroy(cluster *lang.Cluster, deployName string, params util.NestedParameterMap, eventLog *event.Log) error {
	eventLog.WithFields(event.Fields{}).Infof("[-] %s", deployName)
	for _, s := range p.failComponents {
		if strings.Contains(deployName, s) {
			return fmt.Errorf("Delete failed for component: %s", deployName)
		}
	}
	return nil
}

func (p *testPlugin) Endpoints(cluster *lang.Cluster, deployName string, params util.NestedParameterMap, eventLog *event.Log) (map[string]string, error) {
	return nil, nil
}

func (p *testPlugin) Process(desiredPolicy *lang.Policy, desiredState *resolve.PolicyResolution, externalData *external.Data, eventLog *event.Log) error {
	return nil
}
<commit_msg>Fix engine apply test after improving error logging<commit_after>package apply

import (
	"fmt"
	"github.com/Aptomi/aptomi/pkg/slinga/engine/actual"
	"github.com/Aptomi/aptomi/pkg/slinga/engine/diff"
	"github.com/Aptomi/aptomi/pkg/slinga/engine/resolve"
	"github.com/Aptomi/aptomi/pkg/slinga/event"
	"github.com/Aptomi/aptomi/pkg/slinga/external"
	"github.com/Aptomi/aptomi/pkg/slinga/lang"
	"github.com/Aptomi/aptomi/pkg/slinga/lang/builder"
	"github.com/Aptomi/aptomi/pkg/slinga/plugin"
	"github.com/Aptomi/aptomi/pkg/slinga/util"
	"github.com/stretchr/testify/assert"
	"strings"
	"testing"
	"time"
)

const (
	ResSuccess = iota
	ResError   = iota
)

func TestApplyComponentCreateSuccess(t *testing.T) {
	// resolve empty policy
	bActual := builder.NewPolicyBuilder()
	actualState := resolvePolicy(t, bActual)

	// resolve full policy
	bDesired := makePolicyBuilder()
	desiredState := resolvePolicy(t, bDesired)

	// process all actions
	actions := diff.NewPolicyResolutionDiff(desiredState, actualState, 0).Actions

	applier := NewEngineApply(
		bDesired.Policy(),
		desiredState,
		bActual.Policy(),
		actualState,
		actual.NewNoOpActionStateUpdater(),
		bDesired.External(),
		MockPluginFailOnComponent(),
		actions,
	)

	// check actual state
	assert.Equal(t, 0, len(actualState.ComponentInstanceMap), "Actual state should be empty")

	// check that policy apply finished with expected results
	actualState = applyAndCheck(t, applier, ResSuccess, 0, "Successfully resolved")

	// check that actual state got updated
	assert.Equal(t, 2, len(actualState.ComponentInstanceMap), "Actual state should not be empty after apply()")
}

func TestApplyComponentCreateFailure(t *testing.T) {
	// resolve empty policy
	bActual := builder.NewPolicyBuilder()
	actualState := resolvePolicy(t, bActual)

	// resolve full policy
	bDesired := makePolicyBuilder()
	desiredState := resolvePolicy(t, bDesired)

	// process all actions (and make component fail deployment)
	actions := diff.NewPolicyResolutionDiff(desiredState, actualState, 0).Actions
	applier := NewEngineApply(
		bDesired.Policy(),
		desiredState,
		bActual.Policy(),
		actualState,
		actual.NewNoOpActionStateUpdater(),
		bDesired.External(),
		MockPluginFailOnComponent(bDesired.Policy().GetObjectsByKind(lang.ServiceObject.Kind)[0].(*lang.Service).Components[0].Name),
		actions,
	)

	// check actual state
	assert.Equal(t, 0, len(actualState.ComponentInstanceMap), "Actual state should be empty")

	// check that policy apply finished with expected results
	actualState = applyAndCheck(t, applier, ResError, 2, "Apply failed for component")

	// check that actual state got updated (service component exists, but no child components got deployed)
	assert.Equal(t, 1, len(actualState.ComponentInstanceMap), "Actual state should not be empty after apply()")
}

func TestDiffHasUpdatedComponentsAndCheckTimes(t *testing.T) {
	/*
		Step 1: actual = empty, desired = test policy, check = kafka update/create times
	*/

	// Create initial empty resolution data (do not resolve any dependencies)
	bActual := builder.NewPolicyBuilder()
	actualState := resolvePolicy(t, bActual)

	// Resolve all dependencies in policy
	bDesired := makePolicyBuilder()
	desiredState := resolvePolicy(t, bDesired)

	// Apply to update component times in actual state
	applier := NewEngineApply(
		bDesired.Policy(),
		desiredState,
		bActual.Policy(),
		actualState,
		actual.NewNoOpActionStateUpdater(),
		bDesired.External(),
		MockPluginFailOnComponent(),
		diff.NewPolicyResolutionDiff(desiredState, actualState, 0).Actions,
	)

	// Check that policy apply finished with expected results
	updatedActualState := applyAndCheck(t, applier, ResSuccess, 0, "Successfully resolved")

	// Get key to a component
	cluster := bDesired.Policy().GetObjectsByKind(lang.ClusterObject.Kind)[0].(*lang.Cluster)
	contract := bDesired.Policy().GetObjectsByKind(lang.ContractObject.Kind)[0].(*lang.Contract)
	service := bDesired.Policy().GetObjectsByKind(lang.ServiceObject.Kind)[0].(*lang.Service)
	key := resolve.NewComponentInstanceKey(cluster, contract, contract.Contexts[0], nil, service, service.Components[0])
	keyService := key.GetParentServiceKey()

	// Check creation/update times
	times1 := getTimes(t, key.GetKey(), updatedActualState)
	assert.WithinDuration(t, time.Now(), times1.created, time.Second, "Creation time should be initialized correctly")
	assert.Equal(t, times1.updated, times1.updated, "Update time should be equal to creation time")

	actualState = updatedActualState
	bActual = bDesired

	/*
		Step 2: desired = add a dependency, check = component update/create times remained the same in actual state
	*/

	// Sleep a little bit to introduce time delay
	time.Sleep(25 * time.Millisecond)

	// Add another dependency, resolve, calculate difference against prev resolution data
	bDesiredNext := makePolicyBuilder()
	dependencyNew := bDesiredNext.AddDependency(bDesiredNext.AddUser(), contract)
	dependencyNew.Labels["param"] = "value1"

	desiredStateNext := resolvePolicy(t, bDesiredNext)
	assert.Contains(t, desiredStateNext.DependencyInstanceMap, dependencyNew.GetKey(), "New dependency should also be resolved")

	// Apply to update component times in actual state
	applier = NewEngineApply(
		bDesiredNext.Policy(),
		desiredStateNext,
		bActual.Policy(),
		actualState,
		actual.NewNoOpActionStateUpdater(),
		bDesiredNext.External(),
		MockPluginFailOnComponent(),
		diff.NewPolicyResolutionDiff(desiredStateNext, actualState, 0).Actions,
	)

	// Check that policy apply finished with expected results
	updatedActualState = applyAndCheck(t, applier, ResSuccess, 0, "Successfully resolved")

	// Check creation/update times
	times2 := getTimes(t, key.GetKey(), updatedActualState)
	assert.Equal(t, times1.created, times2.created, "Creation time should be carried over to remain the same")
	assert.Equal(t, times1.updated, times2.updated, "Update time should be carried over to remain the same")

	actualState = updatedActualState
	bActual = bDesiredNext

	/*
		Step 3: desired = update user label, check = component update time changed
	*/
	componentTimes := getTimes(t, key.GetKey(), actualState)
	serviceTimes := getTimes(t, keyService.GetKey(), actualState)

	// Sleep a little bit to introduce time delay
	time.Sleep(25 * time.Millisecond)

	// Update labels, re-evaluate and see that component instance has changed
	for _, dependency := range bDesiredNext.Policy().GetObjectsByKind(lang.DependencyObject.Kind) {
		dependency.(*lang.Dependency).Labels["param"] = "value2"
	}
	desiredStateAfterUpdate := resolvePolicy(t, bDesiredNext)

	// Apply to update component times in actual state
	applier = NewEngineApply(
		bDesiredNext.Policy(),
		desiredStateAfterUpdate,
		bActual.Policy(),
		actualState,
		actual.NewNoOpActionStateUpdater(),
		bDesiredNext.External(),
		MockPluginFailOnComponent(),
		diff.NewPolicyResolutionDiff(desiredStateAfterUpdate, actualState, 0).Actions,
	)

	// Check that policy apply finished with expected results
	updatedActualState = applyAndCheck(t, applier, ResSuccess, 0, "Successfully resolved")

	// Check creation/update times for component
	componentTimesUpdated := getTimes(t, key.GetKey(), updatedActualState)
	assert.Equal(t, componentTimes.created, componentTimesUpdated.created, "Creation time for component should be carried over to remain the same")
	assert.True(t, componentTimesUpdated.updated.After(componentTimes.updated), "Update time for component should be changed")

	// Check creation/update times for service
	serviceTimesUpdated := getTimes(t, keyService.GetKey(), updatedActualState)
	assert.Equal(t, serviceTimes.created, serviceTimesUpdated.created, "Creation time for parent service should be carried over to remain the same")
	assert.True(t, serviceTimesUpdated.updated.After(serviceTimes.updated), "Update time for parent service should be changed")
}

/*
	Helpers
*/

func makePolicyBuilder() *builder.PolicyBuilder {
	b := builder.NewPolicyBuilder()

	// create a service
	service := b.AddService(b.AddUser())
	b.AddServiceComponent(service,
		b.CodeComponent(
			util.NestedParameterMap{
				"param":   "{{ .Labels.param }}",
				"cluster": "{{ .Labels.cluster }}",
			},
			nil,
		),
	)
	contract := b.AddContract(service, b.CriteriaTrue())

	// add rules to allow all dependencies
	clusterObj := b.AddCluster()
	b.AddRule(b.CriteriaTrue(), b.RuleActions(lang.Allow, lang.Allow, lang.NewLabelOperationsSetSingleLabel(lang.LabelCluster, clusterObj.Name)))

	// add dependency
	dependency := b.AddDependency(b.AddUser(), contract)
	dependency.Labels["param"] = "value1"

	return b
}

func resolvePolicy(t *testing.T, b *builder.PolicyBuilder) *resolve.PolicyResolution {
	t.Helper()
	resolver := resolve.NewPolicyResolver(b.Policy(), b.External())
	result, eventLog, err := resolver.ResolveAllDependencies()
	if !assert.NoError(t, err, "Policy should be resolved without errors") {
		hook := &event.HookStdout{}
		eventLog.Save(hook)
		t.FailNow()
	}

	return result
}

func applyAndCheck(t *testing.T, apply *EngineApply, expectedResult int, errorCnt int, expectedMessage string) *resolve.PolicyResolution {
	t.Helper()
	actualState, eventLog, err := apply.Apply()

	if !assert.Equal(t, expectedResult != ResError, err == nil, "Apply status (success vs. error)") {
		// print log into stdout and exit
		hook := &event.HookStdout{}
		eventLog.Save(hook)
		t.FailNow()
	}

	if expectedResult == ResError {
		// check for error messages
		verifier := event.NewUnitTestLogVerifier(expectedMessage, expectedResult == ResError)
		eventLog.Save(verifier)
		if !assert.Equal(t, errorCnt, verifier.MatchedErrorsCount(), "Apply event log should have correct number of messages containing words: "+expectedMessage) {
			hook := &event.HookStdout{}
			eventLog.Save(hook)
			t.FailNow()
		}
	}
	return actualState
}

type componentTimes struct {
	created time.Time
	updated time.Time
}

func getTimes(t *testing.T, key string, u2 *resolve.PolicyResolution) componentTimes {
	t.Helper()
	return componentTimes{
		created: getInstanceInternal(t, key, u2).CreatedOn,
		updated: getInstanceInternal(t, key, u2).UpdatedOn,
	}
}

func getInstanceInternal(t *testing.T, key string, resolution *resolve.PolicyResolution) *resolve.ComponentInstance {
	t.Helper()
	instance, ok := resolution.ComponentInstanceMap[key]
	if !assert.True(t, ok, "Component instance exists in resolution data: "+key) {
		t.FailNow()
	}
	return instance
}

func MockPluginFailOnComponent(failComponents ...string) plugin.Registry {
	return &testRegistry{&testPlugin{failComponents}}
}

type testRegistry struct {
	*testPlugin
}

func (reg *testRegistry) GetDeployPlugin(codeType string) (plugin.DeployPlugin, error) {
	return reg.testPlugin, nil
}

func (reg *testRegistry) GetClustersPostProcessingPlugins() []plugin.ClustersPostProcessPlugin {
	return []plugin.ClustersPostProcessPlugin{reg.testPlugin}
}

type testPlugin struct {
	failComponents []string
}

func (p *testPlugin) Cleanup() error {
	return nil
}

func (p *testPlugin) GetSupportedCodeTypes() []string {
	return []string{}
}

func (p *testPlugin) Create(cluster *lang.Cluster, deployName string, params util.NestedParameterMap, eventLog *event.Log) error {
	eventLog.WithFields(event.Fields{}).Infof("[+] %s", deployName)
	for _, s := range p.failComponents {
		if strings.Contains(deployName, s) {
			return fmt.Errorf("Apply failed for component: %s", deployName)
		}
	}
	return nil
}

func (p *testPlugin) Update(cluster *lang.Cluster, deployName string, params util.NestedParameterMap, eventLog *event.Log) error {
	eventLog.WithFields(event.Fields{}).Infof("[*] %s", deployName)
	for _, s := range p.failComponents {
		if strings.Contains(deployName, s) {
			return fmt.Errorf("Update failed for component: %s", deployName)
		}
	}
	return nil
}

func (p *testPlugin) Destroy(cluster *lang.Cluster, deployName string, params util.NestedParameterMap, eventLog *event.Log) error {
	eventLog.WithFields(event.Fields{}).Infof("[-] %s", deployName)
	for _, s := range p.failComponents {
		if strings.Contains(deployName, s) {
			return fmt.Errorf("Delete failed for component: %s", deployName)
		}
	}
	return nil
}

func (p *testPlugin) Endpoints(cluster *lang.Cluster, deployName string, params util.NestedParameterMap, eventLog *event.Log) (map[string]string, error) {
	return nil, nil
}

func (p *testPlugin) Process(desiredPolicy *lang.Policy, desiredState *resolve.PolicyResolution, externalData *external.Data, eventLog *event.Log) error {
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os"
	"text/template"
)

type Source struct {
	api  *Api
	tmpl *template.Template
}

func NewSource(api *Api, templateFilename string) *Source {
	source := new(Source)

	source.api = api
	source.tmpl = template.Must(template.ParseGlob(templateFilename))

	return source
}

func (s *Source) SaveToFile(templateFilename string) error {

	// Replace tokens in filename.
	t := template.Must(template.New("template_filename").Parse(templateFilename))

	buffFilename := bytes.NewBufferString("")
	t.Execute(buffFilename, s.api)

	filename := buffFilename.String()

	log.Printf("Generating %s...", filename)

	if err := CreateBasePath(filename); err != nil {
		return errors.New(fmt.Sprintf("creating base for %s: %s", filename, err))
	}

	f, err := os.Create(filename)
	if err != nil {
		return errors.New(fmt.Sprintf("create file: %s", err))
	}

	defer f.Close()

	err = s.tmpl.Execute(f, s.api)
	if err != nil {
		return errors.New(fmt.Sprintf("template execution: %s", err))
	}

	return nil
}
<commit_msg>When saving file, base path is added to the file.<commit_after>package main

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os"
	"text/template"
)

type Source struct {
	api  *Api
	tmpl *template.Template
}

func NewSource(api *Api, templateFilename string) *Source {
	source := new(Source)

	source.api = api
	source.tmpl = template.Must(template.ParseGlob(templateFilename))

	return source
}

func (s *Source) SaveToFile(templateFilename string) error {

	baseSourceDir := s.api.OutputDir + "/"

	// Replace tokens in filename.
	t := template.Must(template.New("template_filename").Parse(templateFilename))

	buffFilename := bytes.NewBufferString("")
	t.Execute(buffFilename, s.api)

	filename := baseSourceDir + buffFilename.String()

	log.Printf("Generating %s", filename)

	if err := CreateBasePath(filename); err != nil {
		return errors.New(fmt.Sprintf("creating base for %s: %s", filename, err))
	}

	f, err := os.Create(filename)
	if err != nil {
		return errors.New(fmt.Sprintf("create file: %s", err))
	}

	defer f.Close()

	err = s.tmpl.Execute(f, s.api)
	if err != nil {
		return errors.New(fmt.Sprintf("template execution: %s", err))
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright The Helm 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 strvals

import (
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"

	"github.com/pkg/errors"
	"sigs.k8s.io/yaml"
)

// ErrNotList indicates that a non-list was treated as a list.
var ErrNotList = errors.New("not a list")

// ToYAML takes a string of arguments and converts to a YAML document.
func ToYAML(s string) (string, error) {
	m, err := Parse(s)
	if err != nil {
		return "", err
	}
	d, err := yaml.Marshal(m)
	return strings.TrimSuffix(string(d), "\n"), err
}

// Parse parses a set line.
//
// A set line is of the form name1=value1,name2=value2
func Parse(s string) (map[string]interface{}, error) {
	vals := map[string]interface{}{}
	scanner := bytes.NewBufferString(s)
	t := newParser(scanner, vals, false)
	err := t.parse()
	return vals, err
}

// ParseString parses a set line and forces a string value.
//
// A set line is of the form name1=value1,name2=value2
func ParseString(s string) (map[string]interface{}, error) {
	vals := map[string]interface{}{}
	scanner := bytes.NewBufferString(s)
	t := newParser(scanner, vals, true)
	err := t.parse()
	return vals, err
}

// ParseInto parses a strvals line and merges the result into dest.
//
// If the strval string has a key that exists in dest, it overwrites the
// dest version.
func ParseInto(s string, dest map[string]interface{}) error {
	scanner := bytes.NewBufferString(s)
	t := newParser(scanner, dest, false)
	return t.parse()
}

// ParseFile parses a set line, but its final value is loaded from the file at the path specified by the original value.
//
// A set line is of the form name1=path1,name2=path2
//
// When the files at path1 and path2 contained "val1" and "val2" respectively, the set line is consumed as
// name1=val1,name2=val2
func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error) {
	vals := map[string]interface{}{}
	scanner := bytes.NewBufferString(s)
	t := newFileParser(scanner, vals, reader)
	err := t.parse()
	return vals, err
}

// ParseIntoString parses a strvals line and merges the result into dest.
//
// This method always returns a string as the value.
func ParseIntoString(s string, dest map[string]interface{}) error {
	scanner := bytes.NewBufferString(s)
	t := newParser(scanner, dest, true)
	return t.parse()
}

// ParseIntoFile parses a filevals line and merges the result into dest.
//
// This method always returns a string as the value.
func ParseIntoFile(s string, dest map[string]interface{}, reader RunesValueReader) error {
	scanner := bytes.NewBufferString(s)
	t := newFileParser(scanner, dest, reader)
	return t.parse()
}

// RunesValueReader is a function that takes the given value (a slice of runes)
// and returns the parsed value
type RunesValueReader func([]rune) (interface{}, error)

// parser is a simple parser that takes a strvals line and parses it into a
// map representation.
//
// where sc is the source of the original data being parsed
// where data is the final parsed data from the parses with correct types
type parser struct {
	sc     *bytes.Buffer
	data   map[string]interface{}
	reader RunesValueReader
}

func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser {
	stringConverter := func(rs []rune) (interface{}, error) {
		return typedVal(rs, stringBool), nil
	}
	return &parser{sc: sc, data: data, reader: stringConverter}
}

func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser {
	return &parser{sc: sc, data: data, reader: reader}
}

func (t *parser) parse() error {
	for {
		err := t.key(t.data)
		if err == nil {
			continue
		}
		if err == io.EOF {
			return nil
		}
		return err
	}
}

func runeSet(r []rune) map[rune]bool {
	s := make(map[rune]bool, len(r))
	for _, rr := range r {
		s[rr] = true
	}
	return s
}

func (t *parser) key(data map[string]interface{}) (reterr error) {
	defer func() {
		if r := recover(); r != nil {
			reterr = fmt.Errorf("unable to parse key: %s", r)
		}
	}()
	stop := runeSet([]rune{'=', '[', ',', '.'})
	for {
		switch k, last, err := runesUntil(t.sc, stop); {
		case err != nil:
			if len(k) == 0 {
				return err
			}
			return errors.Errorf("key %q has no value", string(k))
			//set(data, string(k), "")
			//return err
		case last == '[':
			// We are in a list index context, so we need to set an index.
			i, err := t.keyIndex()
			if err != nil {
				return errors.Wrap(err, "error parsing index")
			}
			kk := string(k)
			// Find or create target list
			list := []interface{}{}
			if _, ok := data[kk]; ok {
				list = data[kk].([]interface{})
			}

			// Now we need to get the value after the ].
			list, err = t.listItem(list, i)
			set(data, kk, list)
			return err
		case last == '=':
			//End of key. Consume =, Get value.
			// FIXME: Get value list first
			vl, e := t.valList()
			switch e {
			case nil:
				set(data, string(k), vl)
				return nil
			case io.EOF:
				set(data, string(k), "")
				return e
			case ErrNotList:
				rs, e := t.val()
				if e != nil && e != io.EOF {
					return e
				}
				v, e := t.reader(rs)
				set(data, string(k), v)
				return e
			default:
				return e
			}

		case last == ',':
			// No value given. Set the value to empty string. Return error.
			set(data, string(k), "")
			return errors.Errorf("key %q has no value (cannot end with ,)", string(k))
		case last == '.':
			// First, create or find the target map.
			inner := map[string]interface{}{}
			if _, ok := data[string(k)]; ok {
				inner = data[string(k)].(map[string]interface{})
			}

			// Recurse
			e := t.key(inner)
			if len(inner) == 0 {
				return errors.Errorf("key map %q has no value", string(k))
			}
			set(data, string(k), inner)
			return e
		}
	}
}

func set(data map[string]interface{}, key string, val interface{}) {
	// If key is empty, don't set it.
	if len(key) == 0 {
		return
	}
	data[key] = val
}

func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{}, err error) {
	// There are possible index values that are out of range on a target system
	// causing a panic. This will catch the panic and return an error instead.
	// The value of the index that causes a panic varies from system to system.
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("error processing index %d: %s", index, r)
		}
	}()

	if index < 0 {
		return list, fmt.Errorf("negative %d index not allowed", index)
	}
	if len(list) <= index {
		newlist := make([]interface{}, index+1)
		copy(newlist, list)
		list = newlist
	}
	list[index] = val
	return list, nil
}

func (t *parser) keyIndex() (int, error) {
	// First, get the key.
	stop := runeSet([]rune{']'})
	v, _, err := runesUntil(t.sc, stop)
	if err != nil {
		return 0, err
	}
	// v should be the index
	return strconv.Atoi(string(v))

}
func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
	if i < 0 {
		return list, fmt.Errorf("negative %d index not allowed", i)
	}
	stop := runeSet([]rune{'[', '.', '='})
	switch k, last, err := runesUntil(t.sc, stop); {
	case len(k) > 0:
		return list, errors.Errorf("unexpected data at end of array index: %q", k)
	case err != nil:
		return list, err
	case last == '=':
		vl, e := t.valList()
		switch e {
		case nil:
			return setIndex(list, i, vl)
		case io.EOF:
			return setIndex(list, i, "")
		case ErrNotList:
			rs, e := t.val()
			if e != nil && e != io.EOF {
				return list, e
			}
			v, e := t.reader(rs)
			if e != nil {
				return list, e
			}
			return setIndex(list, i, v)
		default:
			return list, e
		}
	case last == '[':
		// now we have a nested list. Read the index and handle.
		i, err := t.keyIndex()
		if err != nil {
			return list, errors.Wrap(err, "error parsing index")
		}
		var crtList []interface{}
		if len(list) > i {
			// If nested list already exists, take the value of list to next cycle.
			crtList = list[i].([]interface{})
		} else {
			crtList = list
		}
		// Now we need to get the value after the ].
		list2, err := t.listItem(crtList, i)
		if err != nil {
			return list, err
		}
		return setIndex(list, i, list2)
	case last == '.':
		// We have a nested object. Send to t.key
		inner := map[string]interface{}{}
		if len(list) > i {
			var ok bool
			inner, ok = list[i].(map[string]interface{})
			if !ok {
				// We have indices out of order. Initialize empty value.
				list[i] = map[string]interface{}{}
				inner = list[i].(map[string]interface{})
			}
		}

		// Recurse
		e := t.key(inner)
		if e != nil {
			return list, e
		}
		return setIndex(list, i, inner)
	default:
		return nil, errors.Errorf("parse error: unexpected token %v", last)
	}
}

func (t *parser) val() ([]rune, error) {
	stop := runeSet([]rune{','})
	v, _, err := runesUntil(t.sc, stop)
	return v, err
}

func (t *parser) valList() ([]interface{}, error) {
	r, _, e := t.sc.ReadRune()
	if e != nil {
		return []interface{}{}, e
	}

	if r != '{' {
		t.sc.UnreadRune()
		return []interface{}{}, ErrNotList
	}

	list := []interface{}{}
	stop := runeSet([]rune{',', '}'})
	for {
		switch rs, last, err := runesUntil(t.sc, stop); {
		case err != nil:
			if err == io.EOF {
				err = errors.New("list must terminate with '}'")
			}
			return list, err
		case last == '}':
			// If this is followed by ',', consume it.
			if r, _, e := t.sc.ReadRune(); e == nil && r != ',' {
				t.sc.UnreadRune()
			}
			v, e := t.reader(rs)
			list = append(list, v)
			return list, e
		case last == ',':
			v, e := t.reader(rs)
			if e != nil {
				return list, e
			}
			list = append(list, v)
		}
	}
}

func runesUntil(in io.RuneReader, stop map[rune]bool) ([]rune, rune, error) {
	v := []rune{}
	for {
		switch r, _, e := in.ReadRune(); {
		case e != nil:
			return v, r, e
		case inMap(r, stop):
			return v, r, nil
		case r == '\\':
			next, _, e := in.ReadRune()
			if e != nil {
				return v, next, e
			}
			v = append(v, next)
		default:
			v = append(v, r)
		}
	}
}

func inMap(k rune, m map[rune]bool) bool {
	_, ok := m[k]
	return ok
}

func typedVal(v []rune, st bool) interface{} {
	val := string(v)

	if st {
		return val
	}

	if strings.EqualFold(val, "true") {
		return true
	}

	if strings.EqualFold(val, "false") {
		return false
	}

	if strings.EqualFold(val, "null") {
		return nil
	}

	if strings.EqualFold(val, "0") {
		return int64(0)
	}

	// If this value does not start with zero, try parsing it to an int
	if len(val) != 0 && val[0] != '0' {
		if iv, err := strconv.ParseInt(val, 10, 64); err == nil {
			return iv
		}
	}

	return val
}
<commit_msg>fix another extreme case<commit_after>/*
Copyright The Helm 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 strvals

import (
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"

	"github.com/pkg/errors"
	"sigs.k8s.io/yaml"
)

// ErrNotList indicates that a non-list was treated as a list.
var ErrNotList = errors.New("not a list")

// ToYAML takes a string of arguments and converts to a YAML document.
func ToYAML(s string) (string, error) {
	m, err := Parse(s)
	if err != nil {
		return "", err
	}
	d, err := yaml.Marshal(m)
	return strings.TrimSuffix(string(d), "\n"), err
}

// Parse parses a set line.
//
// A set line is of the form name1=value1,name2=value2
func Parse(s string) (map[string]interface{}, error) {
	vals := map[string]interface{}{}
	scanner := bytes.NewBufferString(s)
	t := newParser(scanner, vals, false)
	err := t.parse()
	return vals, err
}

// ParseString parses a set line and forces a string value.
//
// A set line is of the form name1=value1,name2=value2
func ParseString(s string) (map[string]interface{}, error) {
	vals := map[string]interface{}{}
	scanner := bytes.NewBufferString(s)
	t := newParser(scanner, vals, true)
	err := t.parse()
	return vals, err
}

// ParseInto parses a strvals line and merges the result into dest.
//
// If the strval string has a key that exists in dest, it overwrites the
// dest version.
func ParseInto(s string, dest map[string]interface{}) error {
	scanner := bytes.NewBufferString(s)
	t := newParser(scanner, dest, false)
	return t.parse()
}

// ParseFile parses a set line, but its final value is loaded from the file at the path specified by the original value.
//
// A set line is of the form name1=path1,name2=path2
//
// When the files at path1 and path2 contained "val1" and "val2" respectively, the set line is consumed as
// name1=val1,name2=val2
func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error) {
	vals := map[string]interface{}{}
	scanner := bytes.NewBufferString(s)
	t := newFileParser(scanner, vals, reader)
	err := t.parse()
	return vals, err
}

// ParseIntoString parses a strvals line and merges the result into dest.
//
// This method always returns a string as the value.
func ParseIntoString(s string, dest map[string]interface{}) error {
	scanner := bytes.NewBufferString(s)
	t := newParser(scanner, dest, true)
	return t.parse()
}

// ParseIntoFile parses a filevals line and merges the result into dest.
//
// This method always returns a string as the value.
func ParseIntoFile(s string, dest map[string]interface{}, reader RunesValueReader) error {
	scanner := bytes.NewBufferString(s)
	t := newFileParser(scanner, dest, reader)
	return t.parse()
}

// RunesValueReader is a function that takes the given value (a slice of runes)
// and returns the parsed value
type RunesValueReader func([]rune) (interface{}, error)

// parser is a simple parser that takes a strvals line and parses it into a
// map representation.
//
// where sc is the source of the original data being parsed
// where data is the final parsed data from the parses with correct types
type parser struct {
	sc     *bytes.Buffer
	data   map[string]interface{}
	reader RunesValueReader
}

func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser {
	stringConverter := func(rs []rune) (interface{}, error) {
		return typedVal(rs, stringBool), nil
	}
	return &parser{sc: sc, data: data, reader: stringConverter}
}

func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser {
	return &parser{sc: sc, data: data, reader: reader}
}

func (t *parser) parse() error {
	for {
		err := t.key(t.data)
		if err == nil {
			continue
		}
		if err == io.EOF {
			return nil
		}
		return err
	}
}

func runeSet(r []rune) map[rune]bool {
	s := make(map[rune]bool, len(r))
	for _, rr := range r {
		s[rr] = true
	}
	return s
}

func (t *parser) key(data map[string]interface{}) (reterr error) {
	defer func() {
		if r := recover(); r != nil {
			reterr = fmt.Errorf("unable to parse key: %s", r)
		}
	}()
	stop := runeSet([]rune{'=', '[', ',', '.'})
	for {
		switch k, last, err := runesUntil(t.sc, stop); {
		case err != nil:
			if len(k) == 0 {
				return err
			}
			return errors.Errorf("key %q has no value", string(k))
			//set(data, string(k), "")
			//return err
		case last == '[':
			// We are in a list index context, so we need to set an index.
			i, err := t.keyIndex()
			if err != nil {
				return errors.Wrap(err, "error parsing index")
			}
			kk := string(k)
			// Find or create target list
			list := []interface{}{}
			if _, ok := data[kk]; ok {
				list = data[kk].([]interface{})
			}

			// Now we need to get the value after the ].
			list, err = t.listItem(list, i)
			set(data, kk, list)
			return err
		case last == '=':
			//End of key. Consume =, Get value.
			// FIXME: Get value list first
			vl, e := t.valList()
			switch e {
			case nil:
				set(data, string(k), vl)
				return nil
			case io.EOF:
				set(data, string(k), "")
				return e
			case ErrNotList:
				rs, e := t.val()
				if e != nil && e != io.EOF {
					return e
				}
				v, e := t.reader(rs)
				set(data, string(k), v)
				return e
			default:
				return e
			}

		case last == ',':
			// No value given. Set the value to empty string. Return error.
			set(data, string(k), "")
			return errors.Errorf("key %q has no value (cannot end with ,)", string(k))
		case last == '.':
			// First, create or find the target map.
			inner := map[string]interface{}{}
			if _, ok := data[string(k)]; ok {
				inner = data[string(k)].(map[string]interface{})
			}

			// Recurse
			e := t.key(inner)
			if len(inner) == 0 {
				return errors.Errorf("key map %q has no value", string(k))
			}
			set(data, string(k), inner)
			return e
		}
	}
}

func set(data map[string]interface{}, key string, val interface{}) {
	// If key is empty, don't set it.
	if len(key) == 0 {
		return
	}
	data[key] = val
}

func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{}, err error) {
	// There are possible index values that are out of range on a target system
	// causing a panic. This will catch the panic and return an error instead.
	// The value of the index that causes a panic varies from system to system.
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("error processing index %d: %s", index, r)
		}
	}()

	if index < 0 {
		return list, fmt.Errorf("negative %d index not allowed", index)
	}
	if len(list) <= index {
		newlist := make([]interface{}, index+1)
		copy(newlist, list)
		list = newlist
	}
	list[index] = val
	return list, nil
}

func (t *parser) keyIndex() (int, error) {
	// First, get the key.
	stop := runeSet([]rune{']'})
	v, _, err := runesUntil(t.sc, stop)
	if err != nil {
		return 0, err
	}
	// v should be the index
	return strconv.Atoi(string(v))

}
func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
	if i < 0 {
		return list, fmt.Errorf("negative %d index not allowed", i)
	}
	stop := runeSet([]rune{'[', '.', '='})
	switch k, last, err := runesUntil(t.sc, stop); {
	case len(k) > 0:
		return list, errors.Errorf("unexpected data at end of array index: %q", k)
	case err != nil:
		return list, err
	case last == '=':
		vl, e := t.valList()
		switch e {
		case nil:
			return setIndex(list, i, vl)
		case io.EOF:
			return setIndex(list, i, "")
		case ErrNotList:
			rs, e := t.val()
			if e != nil && e != io.EOF {
				return list, e
			}
			v, e := t.reader(rs)
			if e != nil {
				return list, e
			}
			return setIndex(list, i, v)
		default:
			return list, e
		}
	case last == '[':
		// now we have a nested list. Read the index and handle.
		nextI, err := t.keyIndex()
		if err != nil {
			return list, errors.Wrap(err, "error parsing index")
		}
		var crtList []interface{}
		if len(list) > i {
			// If nested list already exists, take the value of list to next cycle.
			crtList = list[i].([]interface{})
		}
		// Now we need to get the value after the ].
<<<<<<< HEAD
		list2, err := t.listItem(crtList, i)
		if err != nil {
			return list, err
		}
		return setIndex(list, i, list2)
=======
		list2, err := t.listItem(crtList, nextI)
		return setIndex(list, i, list2), err
>>>>>>> fix another extreme case
	case last == '.':
		// We have a nested object. Send to t.key
		inner := map[string]interface{}{}
		if len(list) > i {
			var ok bool
			inner, ok = list[i].(map[string]interface{})
			if !ok {
				// We have indices out of order. Initialize empty value.
				list[i] = map[string]interface{}{}
				inner = list[i].(map[string]interface{})
			}
		}

		// Recurse
		e := t.key(inner)
		if e != nil {
			return list, e
		}
		return setIndex(list, i, inner)
	default:
		return nil, errors.Errorf("parse error: unexpected token %v", last)
	}
}

func (t *parser) val() ([]rune, error) {
	stop := runeSet([]rune{','})
	v, _, err := runesUntil(t.sc, stop)
	return v, err
}

func (t *parser) valList() ([]interface{}, error) {
	r, _, e := t.sc.ReadRune()
	if e != nil {
		return []interface{}{}, e
	}

	if r != '{' {
		t.sc.UnreadRune()
		return []interface{}{}, ErrNotList
	}

	list := []interface{}{}
	stop := runeSet([]rune{',', '}'})
	for {
		switch rs, last, err := runesUntil(t.sc, stop); {
		case err != nil:
			if err == io.EOF {
				err = errors.New("list must terminate with '}'")
			}
			return list, err
		case last == '}':
			// If this is followed by ',', consume it.
			if r, _, e := t.sc.ReadRune(); e == nil && r != ',' {
				t.sc.UnreadRune()
			}
			v, e := t.reader(rs)
			list = append(list, v)
			return list, e
		case last == ',':
			v, e := t.reader(rs)
			if e != nil {
				return list, e
			}
			list = append(list, v)
		}
	}
}

func runesUntil(in io.RuneReader, stop map[rune]bool) ([]rune, rune, error) {
	v := []rune{}
	for {
		switch r, _, e := in.ReadRune(); {
		case e != nil:
			return v, r, e
		case inMap(r, stop):
			return v, r, nil
		case r == '\\':
			next, _, e := in.ReadRune()
			if e != nil {
				return v, next, e
			}
			v = append(v, next)
		default:
			v = append(v, r)
		}
	}
}

func inMap(k rune, m map[rune]bool) bool {
	_, ok := m[k]
	return ok
}

func typedVal(v []rune, st bool) interface{} {
	val := string(v)

	if st {
		return val
	}

	if strings.EqualFold(val, "true") {
		return true
	}

	if strings.EqualFold(val, "false") {
		return false
	}

	if strings.EqualFold(val, "null") {
		return nil
	}

	if strings.EqualFold(val, "0") {
		return int64(0)
	}

	// If this value does not start with zero, try parsing it to an int
	if len(val) != 0 && val[0] != '0' {
		if iv, err := strconv.ParseInt(val, 10, 64); err == nil {
			return iv
		}
	}

	return val
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/olekukonko/tablewriter"

	"github.com/lxc/lxd"
	"github.com/lxc/lxd/i18n"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/gnuflag"
)

type Column struct {
	Name string
	Data columnData
}

type columnData func(shared.ContainerInfo) string

type ByName [][]string

func (a ByName) Len() int {
	return len(a)
}

func (a ByName) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func (a ByName) Less(i, j int) bool {
	if a[i][0] == "" {
		return false
	}

	if a[j][0] == "" {
		return true
	}

	return a[i][0] < a[j][0]
}

type listCmd struct{}

func (c *listCmd) showByDefault() bool {
	return true
}

func (c *listCmd) usage() string {
	return i18n.G(
		`Lists the available resources.

lxc list [resource] [filters] -c [columns]

The filters are:
* A single keyword like "web" which will list any container with "web" in its name.
* A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier:
* "user.blah=abc" will list all containers with the "blah" user property set to "abc"
* "u.blah=abc" will do the same
* "security.privileged=1" will list all privileged containers
* "s.privileged=1" will do the same

The columns are:
* n - name
* s - state
* 4 - IP4
* 6 - IP6
* e - ephemeral
* S - snapshots
* p - pid of container init process`)
}

var chosenColumnRunes string

func (c *listCmd) flags() {
	gnuflag.StringVar(&chosenColumnRunes, "c", "ns46eS", i18n.G("Columns"))
}

// This seems a little excessive.
func dotPrefixMatch(short string, full string) bool {
	fullMembs := strings.Split(full, ".")
	shortMembs := strings.Split(short, ".")

	if len(fullMembs) != len(shortMembs) {
		return false
	}

	for i, _ := range fullMembs {
		if !strings.HasPrefix(fullMembs[i], shortMembs[i]) {
			return false
		}
	}

	return true
}

func shouldShow(filters []string, state *shared.ContainerState) bool {
	for _, filter := range filters {
		if strings.Contains(filter, "=") {
			membs := strings.SplitN(filter, "=", 2)

			key := membs[0]
			var value string
			if len(membs) < 2 {
				value = ""
			} else {
				value = membs[1]
			}

			found := false
			for configKey, configValue := range state.Config {
				if dotPrefixMatch(key, configKey) {
					if value == configValue {
						found = true
						break
					} else {
						// the property was found but didn't match
						return false
					}
				}
			}

			if !found {
				return false
			}
		} else {
			if !strings.Contains(state.Name, filter) {
				return false
			}
		}
	}

	return true
}

func listContainers(cinfos []shared.ContainerInfo, filters []string, columns []Column, listsnaps bool) error {
	headers := []string{}
	for _, column := range columns {
		headers = append(headers, column.Name)
	}

	data := [][]string{}
	for _, cinfo := range cinfos {
		if !shouldShow(filters, &cinfo.State) {
			continue
		}
		d := []string{}
		for _, column := range columns {
			d = append(d, column.Data(cinfo))
		}
		data = append(data, d)
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetAutoWrapText(false)
	table.SetRowLine(true)
	table.SetHeader(headers)
	sort.Sort(ByName(data))
	table.AppendBulk(data)
	table.Render()

	if listsnaps && len(cinfos) == 1 {
		csnaps := cinfos[0].Snaps
		first_snapshot := true
		for _, snap := range csnaps {
			if first_snapshot {
				fmt.Println(i18n.G("Snapshots:"))
			}
			fmt.Printf("  %s\n", snap)
			first_snapshot = false
		}
	}
	return nil
}

func (c *listCmd) run(config *lxd.Config, args []string) error {
	var remote string
	name := ""

	filters := []string{}

	if len(args) != 0 {
		filters = args
		if strings.Contains(args[0], ":") {
			remote, name = config.ParseRemoteAndContainer(args[0])
			filters = args[1:]
		} else if !strings.Contains(args[0], "=") {
			remote = config.DefaultRemote
			name = args[0]
		}
	}

	if remote == "" {
		remote = config.DefaultRemote
	}

	d, err := lxd.NewClient(config, remote)
	if err != nil {
		return err
	}

	var cts []shared.ContainerInfo
	ctslist, err := d.ListContainers()
	if err != nil {
		return err
	}

	if name == "" {
		cts = ctslist
	} else {
		for _, cinfo := range ctslist {
			if len(cinfo.State.Name) >= len(name) && cinfo.State.Name[0:len(name)] == name {
				cts = append(cts, cinfo)
			}
		}
	}

	columns_map := map[rune]Column{
		'n': Column{i18n.G("NAME"), nameColumnData},
		's': Column{i18n.G("STATE"), statusColumnData},
		'4': Column{i18n.G("IPV4"), IP4ColumnData},
		'6': Column{i18n.G("IPV6"), IP6ColumnData},
		'e': Column{i18n.G("EPHEMERAL"), isEphemeralColumnData},
		'S': Column{i18n.G("SNAPSHOTS"), numberSnapshotsColumnData},
		'p': Column{i18n.G("PID"), PIDColumnData},
	}

	columns := []Column{}
	for _, columnRune := range chosenColumnRunes {
		if column, ok := columns_map[columnRune]; ok {
			columns = append(columns, column)
		} else {
			return fmt.Errorf("%s does contain invalid column characters\n", chosenColumnRunes)
		}
	}

	return listContainers(cts, filters, columns, len(cts) == 1)
}

func nameColumnData(cinfo shared.ContainerInfo) string {
	return cinfo.State.Name
}

func statusColumnData(cinfo shared.ContainerInfo) string {
	return strings.ToUpper(cinfo.State.Status.Status)
}

func IP4ColumnData(cinfo shared.ContainerInfo) string {
	if cinfo.State.Status.StatusCode == shared.Running || cinfo.State.Status.StatusCode == shared.Frozen {
		ipv4s := []string{}
		for _, ip := range cinfo.State.Status.Ips {
			if ip.Interface == "lo" {
				continue
			}

			if ip.Protocol == "IPV4" {
				ipv4s = append(ipv4s, fmt.Sprintf("%s (%s)", ip.Address, ip.Interface))
			}
		}
		return strings.Join(ipv4s, "\n")
	} else {
		return ""
	}
}

func IP6ColumnData(cinfo shared.ContainerInfo) string {
	if cinfo.State.Status.StatusCode == shared.Running || cinfo.State.Status.StatusCode == shared.Frozen {
		ipv6s := []string{}
		for _, ip := range cinfo.State.Status.Ips {
			if ip.Interface == "lo" {
				continue
			}

			if ip.Protocol == "IPV6" {
				ipv6s = append(ipv6s, fmt.Sprintf("%s (%s)", ip.Address, ip.Interface))
			}
		}
		return strings.Join(ipv6s, "\n")
	} else {
		return ""
	}
}

func isEphemeralColumnData(cinfo shared.ContainerInfo) string {
	if cinfo.State.Ephemeral {
		return i18n.G("YES")
	} else {
		return i18n.G("NO")
	}
}

func numberSnapshotsColumnData(cinfo shared.ContainerInfo) string {
	return fmt.Sprintf("%d", len(cinfo.Snaps))
}

func PIDColumnData(cinfo shared.ContainerInfo) string {
	if cinfo.State.Status.Init != 0 {
		return fmt.Sprintf("%d", cinfo.State.Status.Init)
	} else {
		return ""
	}
}
<commit_msg>Regex for lxc list filters<commit_after>package main

import (
	"fmt"
	"os"
	"sort"
	"strings"
	"regexp"

	"github.com/olekukonko/tablewriter"

	"github.com/lxc/lxd"
	"github.com/lxc/lxd/i18n"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/gnuflag"
)

type Column struct {
	Name string
	Data columnData
}

type columnData func(shared.ContainerInfo) string

type ByName [][]string

func (a ByName) Len() int {
	return len(a)
}

func (a ByName) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func (a ByName) Less(i, j int) bool {
	if a[i][0] == "" {
		return false
	}

	if a[j][0] == "" {
		return true
	}

	return a[i][0] < a[j][0]
}

type listCmd struct{}

func (c *listCmd) showByDefault() bool {
	return true
}

func (c *listCmd) usage() string {
	return i18n.G(
		`Lists the available resources.

lxc list [resource] [filters] -c [columns]

The filters are:
* A single keyword like "web" which will list any container with "web" in its name.
* A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier:
* "user.blah=abc" will list all containers with the "blah" user property set to "abc"
* "u.blah=abc" will do the same
* "security.privileged=1" will list all privileged containers
* "s.privileged=1" will do the same

The columns are:
* n - name
* s - state
* 4 - IP4
* 6 - IP6
* e - ephemeral
* S - snapshots
* p - pid of container init process`)
}

var chosenColumnRunes string

func (c *listCmd) flags() {
	gnuflag.StringVar(&chosenColumnRunes, "c", "ns46eS", i18n.G("Columns"))
}

// This seems a little excessive.
func dotPrefixMatch(short string, full string) bool {
	fullMembs := strings.Split(full, ".")
	shortMembs := strings.Split(short, ".")

	if len(fullMembs) != len(shortMembs) {
		return false
	}

	for i, _ := range fullMembs {
		if !strings.HasPrefix(fullMembs[i], shortMembs[i]) {
			return false
		}
	}

	return true
}

func shouldShow(filters []string, state *shared.ContainerState) bool {
	for _, filter := range filters {
		if strings.Contains(filter, "=") {
			membs := strings.SplitN(filter, "=", 2)

			key := membs[0]
			var value string
			if len(membs) < 2 {
				value = ""
			} else {
				value = membs[1]
			}

			found := false
			for configKey, configValue := range state.Config {
				if dotPrefixMatch(key, configKey) {
					r, err := regexp.Compile(value)
					if err != nil {
						if value == configValue {
							found = true
							break
						} else {
							// the property was found but didn't match
							return false
						}
					}	else if r.MatchString(configValue) == true {
						found = true
						break
					}
				}
			}

			if !found {
				return false
			}
		} else {
			if !strings.Contains(state.Name, filter) {
				return false
			}
		}
	}

	return true
}

func listContainers(cinfos []shared.ContainerInfo, filters []string, columns []Column, listsnaps bool) error {
	headers := []string{}
	for _, column := range columns {
		headers = append(headers, column.Name)
	}

	data := [][]string{}
	for _, cinfo := range cinfos {
		if !shouldShow(filters, &cinfo.State) {
			continue
		}
		d := []string{}
		for _, column := range columns {
			d = append(d, column.Data(cinfo))
		}
		data = append(data, d)
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetAutoWrapText(false)
	table.SetRowLine(true)
	table.SetHeader(headers)
	sort.Sort(ByName(data))
	table.AppendBulk(data)
	table.Render()

	if listsnaps && len(cinfos) == 1 {
		csnaps := cinfos[0].Snaps
		first_snapshot := true
		for _, snap := range csnaps {
			if first_snapshot {
				fmt.Println(i18n.G("Snapshots:"))
			}
			fmt.Printf("  %s\n", snap)
			first_snapshot = false
		}
	}
	return nil
}

func (c *listCmd) run(config *lxd.Config, args []string) error {
	var remote string
	name := ""

	filters := []string{}

	if len(args) != 0 {
		filters = args
		if strings.Contains(args[0], ":") {
			remote, name = config.ParseRemoteAndContainer(args[0])
			filters = args[1:]
		} else if !strings.Contains(args[0], "=") {
			remote = config.DefaultRemote
			name = args[0]
		}
	}

	if remote == "" {
		remote = config.DefaultRemote
	}

	d, err := lxd.NewClient(config, remote)
	if err != nil {
		return err
	}

	var cts []shared.ContainerInfo
	ctslist, err := d.ListContainers()
	if err != nil {
		return err
	}

	if name == "" {
		cts = ctslist
	} else {
		for _, cinfo := range ctslist {
			if len(cinfo.State.Name) >= len(name) && cinfo.State.Name[0:len(name)] == name {
				cts = append(cts, cinfo)
			}
		}
	}

	columns_map := map[rune]Column{
		'n': Column{i18n.G("NAME"), nameColumnData},
		's': Column{i18n.G("STATE"), statusColumnData},
		'4': Column{i18n.G("IPV4"), IP4ColumnData},
		'6': Column{i18n.G("IPV6"), IP6ColumnData},
		'e': Column{i18n.G("EPHEMERAL"), isEphemeralColumnData},
		'S': Column{i18n.G("SNAPSHOTS"), numberSnapshotsColumnData},
		'p': Column{i18n.G("PID"), PIDColumnData},
	}

	columns := []Column{}
	for _, columnRune := range chosenColumnRunes {
		if column, ok := columns_map[columnRune]; ok {
			columns = append(columns, column)
		} else {
			return fmt.Errorf("%s does contain invalid column characters\n", chosenColumnRunes)
		}
	}

	return listContainers(cts, filters, columns, len(cts) == 1)
}

func nameColumnData(cinfo shared.ContainerInfo) string {
	return cinfo.State.Name
}

func statusColumnData(cinfo shared.ContainerInfo) string {
	return strings.ToUpper(cinfo.State.Status.Status)
}

func IP4ColumnData(cinfo shared.ContainerInfo) string {
	if cinfo.State.Status.StatusCode == shared.Running || cinfo.State.Status.StatusCode == shared.Frozen {
		ipv4s := []string{}
		for _, ip := range cinfo.State.Status.Ips {
			if ip.Interface == "lo" {
				continue
			}

			if ip.Protocol == "IPV4" {
				ipv4s = append(ipv4s, fmt.Sprintf("%s (%s)", ip.Address, ip.Interface))
			}
		}
		return strings.Join(ipv4s, "\n")
	} else {
		return ""
	}
}

func IP6ColumnData(cinfo shared.ContainerInfo) string {
	if cinfo.State.Status.StatusCode == shared.Running || cinfo.State.Status.StatusCode == shared.Frozen {
		ipv6s := []string{}
		for _, ip := range cinfo.State.Status.Ips {
			if ip.Interface == "lo" {
				continue
			}

			if ip.Protocol == "IPV6" {
				ipv6s = append(ipv6s, fmt.Sprintf("%s (%s)", ip.Address, ip.Interface))
			}
		}
		return strings.Join(ipv6s, "\n")
	} else {
		return ""
	}
}

func isEphemeralColumnData(cinfo shared.ContainerInfo) string {
	if cinfo.State.Ephemeral {
		return i18n.G("YES")
	} else {
		return i18n.G("NO")
	}
}

func numberSnapshotsColumnData(cinfo shared.ContainerInfo) string {
	return fmt.Sprintf("%d", len(cinfo.Snaps))
}

func PIDColumnData(cinfo shared.ContainerInfo) string {
	if cinfo.State.Status.Init != 0 {
		return fmt.Sprintf("%d", cinfo.State.Status.Init)
	} else {
		return ""
	}
}
<|endoftext|>