From e9f7e2820761d141c0e14d6e49ead25a8fa6f455 Mon Sep 17 00:00:00 2001 From: Pablo Cantera Date: Wed, 18 Jul 2018 17:54:14 +0200 Subject: [PATCH 1/9] Allowed work with labels on user-mode (issue #77) For these resources: - servers - templates - scripts - ssh_profiles - firewall_profiles --- api/labels/labels_api.go | 110 ++++++++++ api/labels/labels_api_mocked.go | 229 ++++++++++++++++++++ api/labels/labels_api_test.go | 33 +++ api/types/firewall_profiles.go | 13 +- api/types/labels.go | 14 ++ api/types/scripts.go | 13 +- api/types/servers.go | 23 +- api/types/ssh_profiles.go | 11 +- api/types/templates.go | 3 + blueprint/scripts/subcommands.go | 52 +++++ blueprint/templates/subcommands.go | 52 +++++ cloud/servers/subcommands.go | 52 +++++ cloud/ssh_profiles/subcommands.go | 52 +++++ cmd/firewall_profiles_cmd.go | 33 ++- cmd/labels_cmd.go | 255 +++++++++++++++++++++++ cmd/scripts_cmd.go | 35 +++- cmd/servers_cmd.go | 34 ++- cmd/ssh_profiles_cmd.go | 32 ++- cmd/template_cmd.go | 33 ++- labels/subcommands.go | 16 ++ main.go | 9 + network/firewall_profiles/subcommands.go | 52 +++++ testdata/labels_data.go | 24 +++ utils/format/textformatter.go | 27 ++- utils/utils.go | 42 ++++ 25 files changed, 1211 insertions(+), 38 deletions(-) create mode 100644 api/labels/labels_api.go create mode 100644 api/labels/labels_api_mocked.go create mode 100644 api/labels/labels_api_test.go create mode 100644 api/types/labels.go create mode 100644 cmd/labels_cmd.go create mode 100644 labels/subcommands.go create mode 100644 testdata/labels_data.go diff --git a/api/labels/labels_api.go b/api/labels/labels_api.go new file mode 100644 index 0000000..998b65b --- /dev/null +++ b/api/labels/labels_api.go @@ -0,0 +1,110 @@ +package labels + +import ( + "encoding/json" + "fmt" + + log "github.com/Sirupsen/logrus" + "github.com/ingrammicro/concerto/api/types" + "github.com/ingrammicro/concerto/utils" +) + +// LabelService manages polling operations +type LabelService struct { + concertoService utils.ConcertoService +} + +// NewLabelService returns a Concerto labels service +func NewLabelService(concertoService utils.ConcertoService) (*LabelService, error) { + if concertoService == nil { + return nil, fmt.Errorf("Must initialize ConcertoService before using it") + } + + return &LabelService{ + concertoService: concertoService, + }, nil +} + +// GetLabelList returns the list of labels as an array of Label +func (lbl *LabelService) GetLabelList() (labels []types.Label, err error) { + log.Debug("GetLabelList") + + data, status, err := lbl.concertoService.Get("/v1/labels") + if err != nil { + return nil, err + } + + if err = utils.CheckStandardStatus(status, data); err != nil { + return nil, err + } + + if err = json.Unmarshal(data, &labels); err != nil { + return nil, err + } + + // exclude internal labels (with a Namespace defined) + var filteredLabels []types.Label + for _, label := range labels { + if label.Namespace == "" { + filteredLabels = append(filteredLabels, label) + } + } + + return filteredLabels, nil +} + +// CreateLabel creates a label +func (lbl *LabelService) CreateLabel(labelVector *map[string]interface{}) (label *types.Label, err error) { + log.Debug("CreateLabel") + + data, status, err := lbl.concertoService.Post("/v1/labels/", labelVector) + if err != nil { + return nil, err + } + + if err = utils.CheckStandardStatus(status, data); err != nil { + return nil, err + } + + if err = json.Unmarshal(data, &label); err != nil { + return nil, err + } + + return label, nil +} + +// AddLabel assigns a single label from a single labelable resource +func (lbl *LabelService) AddLabel(labelVector *map[string]interface{}, labelID string) (labeledResources []types.LabeledResources, err error) { + log.Debug("AddLabel") + + data, status, err := lbl.concertoService.Post(fmt.Sprintf("/v1/labels/%s/resources", labelID), labelVector) + if err != nil { + return nil, err + } + + if err = utils.CheckStandardStatus(status, data); err != nil { + return nil, err + } + + if err = json.Unmarshal(data, &labeledResources); err != nil { + return nil, err + } + + return labeledResources, nil +} + +// RemoveLabel de-assigns a single label from a single labelable resource +func (lbl *LabelService) RemoveLabel(labelID string, resourceType string, resourceID string) error { + log.Debug("RemoveLabel") + + data, status, err := lbl.concertoService.Delete(fmt.Sprintf("v1/labels/%s/resources/%s/%s", labelID, resourceType, resourceID)) + if err != nil { + return err + } + + if err = utils.CheckStandardStatus(status, data); err != nil { + return err + } + + return nil +} diff --git a/api/labels/labels_api_mocked.go b/api/labels/labels_api_mocked.go new file mode 100644 index 0000000..bf2b615 --- /dev/null +++ b/api/labels/labels_api_mocked.go @@ -0,0 +1,229 @@ +package labels + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/ingrammicro/concerto/api/types" + "github.com/ingrammicro/concerto/utils" + "github.com/stretchr/testify/assert" +) + +// GetLabelListMocked test mocked function +func GetLabelListMocked(t *testing.T, labelsIn *[]types.Label) *[]types.Label { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // to json + dIn, err := json.Marshal(labelsIn) + assert.Nil(err, "Label test data corrupted") + + // call service + cs.On("Get", "/v1/labels").Return(dIn, 200, nil) + labelsOut, err := ds.GetLabelList() + assert.Nil(err, "Error getting labels list") + assert.Equal(*labelsIn, labelsOut, "GetLabelList returned different labels") + + return &labelsOut +} + +// GetLabelListFailErrMocked test mocked function +func GetLabelListFailErrMocked(t *testing.T, labelsIn *[]types.Label) *[]types.Label { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // to json + dIn, err := json.Marshal(labelsIn) + assert.Nil(err, "Label test data corrupted") + + // call service + cs.On("Get", "/v1/labels").Return(dIn, 200, fmt.Errorf("Mocked error")) + labelsOut, err := ds.GetLabelList() + + assert.NotNil(err, "We are expecting an error") + assert.Nil(labelsOut, "Expecting nil output") + assert.Equal(err.Error(), "Mocked error", "Error should be 'Mocked error'") + + return &labelsOut +} + +// GetLabelListFailStatusMocked test mocked function +func GetLabelListFailStatusMocked(t *testing.T, labelsIn *[]types.Label) *[]types.Label { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // to json + dIn, err := json.Marshal(labelsIn) + assert.Nil(err, "Label test data corrupted") + + // call service + cs.On("Get", "/v1/labels").Return(dIn, 499, nil) + labelsOut, err := ds.GetLabelList() + + assert.NotNil(err, "We are expecting an status code error") + assert.Nil(labelsOut, "Expecting nil output") + assert.Contains(err.Error(), "499", "Error should contain http code 499") + + return &labelsOut +} + +// GetLabelListFailJSONMocked test mocked function +func GetLabelListFailJSONMocked(t *testing.T, labelsIn *[]types.Label) *[]types.Label { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // wrong json + dIn := []byte{10, 20, 30} + + // call service + cs.On("Get", "/v1/labels").Return(dIn, 200, nil) + labelsOut, err := ds.GetLabelList() + + assert.NotNil(err, "We are expecting a marshalling error") + assert.Nil(labelsOut, "Expecting nil output") + assert.Contains(err.Error(), "invalid character", "Error message should include the string 'invalid character'") + + return &labelsOut +} + +// CreateLabelMocked test mocked function +func CreateLabelMocked(t *testing.T, labelIn *types.Label) *types.Label { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // convertMap + mapIn, err := utils.ItemConvertParams(*labelIn) + assert.Nil(err, "Label test data corrupted") + + // to json + dOut, err := json.Marshal(labelIn) + assert.Nil(err, "Label test data corrupted") + + // call service + cs.On("Post", "/v1/labels/", mapIn).Return(dOut, 200, nil) + labelOut, err := ds.CreateLabel(mapIn) + assert.Nil(err, "Error creating label") + assert.Equal(labelIn, labelOut, "CreateLabel returned different labels") + + return labelOut +} + +// CreateLabelFailErrMocked test mocked function +func CreateLabelFailErrMocked(t *testing.T, labelIn *types.Label) *types.Label { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // convertMap + mapIn, err := utils.ItemConvertParams(*labelIn) + assert.Nil(err, "Label test data corrupted") + + // to json + dOut, err := json.Marshal(labelIn) + assert.Nil(err, "Label test data corrupted") + + // call service + cs.On("Post", "/v1/labels/", mapIn).Return(dOut, 200, fmt.Errorf("Mocked error")) + labelOut, err := ds.CreateLabel(mapIn) + + assert.NotNil(err, "We are expecting an error") + assert.Nil(labelOut, "Expecting nil output") + assert.Equal(err.Error(), "Mocked error", "Error should be 'Mocked error'") + + return labelOut +} + +// CreateLabelFailStatusMocked test mocked function +func CreateLabelFailStatusMocked(t *testing.T, labelIn *types.Label) *types.Label { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // convertMap + mapIn, err := utils.ItemConvertParams(*labelIn) + assert.Nil(err, "Label test data corrupted") + + // to json + dOut, err := json.Marshal(labelIn) + assert.Nil(err, "Label test data corrupted") + + // call service + cs.On("Post", "/v1/labels/", mapIn).Return(dOut, 499, nil) + labelOut, err := ds.CreateLabel(mapIn) + + assert.NotNil(err, "We are expecting an status code error") + assert.Nil(labelOut, "Expecting nil output") + assert.Contains(err.Error(), "499", "Error should contain http code 499") + + return labelOut +} + +// // CreateLabelFailJSONMocked test mocked function +// func CreateLabelFailJSONMocked(t *testing.T, labelIn *types.Label) *types.Label { + +// assert := assert.New(t) + +// // wire up +// cs := &utils.MockConcertoService{} +// ds, err := NewLabelService(cs) +// assert.Nil(err, "Couldn't load label service") +// assert.NotNil(ds, "Label service not instanced") + +// // convertMap +// mapIn, err := utils.ItemConvertParams(*labelIn) +// assert.Nil(err, "Label test data corrupted") + +// // wrong json +// dIn := []byte{10, 20, 30} + +// // call service +// cs.On("Post", "/v1/cloud/servers/", mapIn).Return(dIn, 200, nil) +// labelOut, err := ds.CreateLabel(mapIn) + +// assert.NotNil(err, "We are expecting a marshalling error") +// assert.Nil(labelOut, "Expecting nil output") +// assert.Contains(err.Error(), "invalid character", "Error message should include the string 'invalid character'") + +// return labelOut +// } diff --git a/api/labels/labels_api_test.go b/api/labels/labels_api_test.go new file mode 100644 index 0000000..8999aa9 --- /dev/null +++ b/api/labels/labels_api_test.go @@ -0,0 +1,33 @@ +package labels + +import ( + "testing" + + "github.com/ingrammicro/concerto/testdata" + "github.com/stretchr/testify/assert" +) + +func TestNewLabelServiceNil(t *testing.T) { + assert := assert.New(t) + rs, err := NewLabelService(nil) + assert.Nil(rs, "Uninitialized service should return nil") + assert.NotNil(err, "Uninitialized service should return error") +} + +func TestGetCloudProviderList(t *testing.T) { + labelsIn := testdata.GetLabelData() + GetLabelListMocked(t, labelsIn) + GetLabelListFailErrMocked(t, labelsIn) + GetLabelListFailStatusMocked(t, labelsIn) + GetLabelListFailJSONMocked(t, labelsIn) +} + +func TestCreateLabel(t *testing.T) { + labelsIn := testdata.GetLabelData() + for _, labelIn := range *labelsIn { + CreateLabelMocked(t, &labelIn) + CreateLabelFailErrMocked(t, &labelIn) + CreateLabelFailStatusMocked(t, &labelIn) + //CreateLabelFailJSONMocked(t, &labelIn) + } +} diff --git a/api/types/firewall_profiles.go b/api/types/firewall_profiles.go index ef771d6..93230b7 100644 --- a/api/types/firewall_profiles.go +++ b/api/types/firewall_profiles.go @@ -1,11 +1,14 @@ package types type FirewallProfile struct { - ID string `json:"id" header:"ID"` - Name string `json:"name,omitempty" header:"NAME"` - Description string `json:"description,omitempty" header:"DESCRIPTION"` - Default bool `json:"default,omitempty" header:"DEFAULT"` - Rules []Rule `json:"rules,omitempty" header:"RULES" show:"nolist"` + ID string `json:"id" header:"ID"` + Name string `json:"name,omitempty" header:"NAME"` + Description string `json:"description,omitempty" header:"DESCRIPTION"` + Default bool `json:"default,omitempty" header:"DEFAULT"` + Rules []Rule `json:"rules,omitempty" header:"RULES" show:"nolist"` + ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` + LabelIDs []string `json:"label_ids" header:"LABEL_IDS" show:"nolist,noshow"` + Labels []string `json:"labels" header:"LABELS"` } type Rule struct { diff --git a/api/types/labels.go b/api/types/labels.go new file mode 100644 index 0000000..b079714 --- /dev/null +++ b/api/types/labels.go @@ -0,0 +1,14 @@ +package types + +type Label struct { + ID string `json:"id" header:"ID"` + Name string `json:"name" header:"NAME"` + ResourceType string `json:"resource_type" header:"RESOURCE_TYPE"` + Namespace string `json:"namespace" header:"NAMESPACE" show:"nolist"` + Value string `json:"value" header:"VALUE" show:"nolist"` +} + +type LabeledResources struct { + ID string `json:"id" header:"ID"` + ResourceType string `json:"resource_type" header:"RESOURCE_TYPE"` +} diff --git a/api/types/scripts.go b/api/types/scripts.go index 3f1b011..05fbd9d 100644 --- a/api/types/scripts.go +++ b/api/types/scripts.go @@ -2,9 +2,12 @@ package types // Script holds script data type Script struct { - ID string `json:"id" header:"ID"` - Name string `json:"name" header:"NAME"` - Description string `json:"description" header:"DESCRIPTION"` - Code string `json:"code" header:"CODE" show:"nolist"` - Parameters []string `json:"parameters" header:"PARAMETERS"` + ID string `json:"id" header:"ID"` + Name string `json:"name" header:"NAME"` + Description string `json:"description" header:"DESCRIPTION"` + Code string `json:"code" header:"CODE" show:"nolist"` + Parameters []string `json:"parameters" header:"PARAMETERS"` + ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` + LabelIDs []string `json:"label_ids" header:"LABEL_IDS" show:"nolist,noshow"` + Labels []string `json:"labels" header:"LABELS"` } diff --git a/api/types/servers.go b/api/types/servers.go index e752b6b..c008875 100644 --- a/api/types/servers.go +++ b/api/types/servers.go @@ -1,16 +1,19 @@ package types type Server struct { - ID string `json:"id" header:"ID"` - Name string `json:"name" header:"NAME"` - Fqdn string `json:"fqdn" header:"FQDN"` - State string `json:"state" header:"STATE"` - PublicIP string `json:"public_ip" header:"PUBLIC_IP"` - TemplateID string `json:"template_id" header:"TEMPLATE_ID"` - ServerPlanID string `json:"server_plan_id" header:"SERVER_PLAN_ID"` - CloudAccountID string `json:"cloud_account_id" header:"CLOUD_ACCOUNT_ID"` - SSHProfileID string `json:"ssh_profile_id" header:"SSH_PROFILE_ID"` - FirewallProfileID string `json:"firewall_profile_id" header:"FIREWALL_PROFILE_ID"` + ID string `json:"id" header:"ID"` + Name string `json:"name" header:"NAME"` + Fqdn string `json:"fqdn" header:"FQDN"` + State string `json:"state" header:"STATE"` + PublicIP string `json:"public_ip" header:"PUBLIC_IP"` + TemplateID string `json:"template_id" header:"TEMPLATE_ID"` + ServerPlanID string `json:"server_plan_id" header:"SERVER_PLAN_ID"` + CloudAccountID string `json:"cloud_account_id" header:"CLOUD_ACCOUNT_ID"` + SSHProfileID string `json:"ssh_profile_id" header:"SSH_PROFILE_ID"` + FirewallProfileID string `json:"firewall_profile_id" header:"FIREWALL_PROFILE_ID"` + ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` + LabelIDs []string `json:"label_ids" header:"LABEL_IDS" show:"nolist,noshow"` + Labels []string `json:"labels" header:"LABELS"` } type Dns struct { diff --git a/api/types/ssh_profiles.go b/api/types/ssh_profiles.go index ef9c3cb..04a2091 100644 --- a/api/types/ssh_profiles.go +++ b/api/types/ssh_profiles.go @@ -1,8 +1,11 @@ package types type SSHProfile struct { - ID string `json:"id" header:"ID"` - Name string `json:"name" heade:"NAME"` - PublicKey string `json:"public_key" header:"PUBLIC_KEY"` - PrivateKey string `json:"private_key" header:"PRIVATE_KEY"` + ID string `json:"id" header:"ID"` + Name string `json:"name" header:"NAME"` + PublicKey string `json:"public_key" header:"PUBLIC_KEY"` + PrivateKey string `json:"private_key" header:"PRIVATE_KEY" show:"nolist"` + ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` + LabelIDs []string `json:"label_ids" header:"LABEL_IDS" show:"nolist,noshow"` + Labels []string `json:"labels" header:"LABELS"` } diff --git a/api/types/templates.go b/api/types/templates.go index 52ecfec..85ff931 100644 --- a/api/types/templates.go +++ b/api/types/templates.go @@ -11,6 +11,9 @@ type Template struct { GenericImageID string `json:"generic_image_id,omitempty" header:"GENERIC IMAGE ID"` ServiceList []string `json:"service_list,omitempty" header:"SERVICE LIST" show:"nolist"` ConfigurationAttributes *json.RawMessage `json:"configuration_attributes,omitempty" header:"CONFIGURATION ATTRIBUTES" show:"nolist"` + ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` + LabelIDs []string `json:"label_ids" header:"LABEL_IDS" show:"nolist,noshow"` + Labels []string `json:"labels" header:"LABELS"` } // TemplateScript stores a templates' script info diff --git a/blueprint/scripts/subcommands.go b/blueprint/scripts/subcommands.go index f635a09..d5f5aec 100644 --- a/blueprint/scripts/subcommands.go +++ b/blueprint/scripts/subcommands.go @@ -11,6 +11,12 @@ func SubCommands() []cli.Command { Name: "list", Usage: "Lists all available scripts", Action: cmd.ScriptsList, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "labels", + Usage: "A list of comma separated label as a query filter", + }, + }, }, { Name: "show", @@ -44,6 +50,10 @@ func SubCommands() []cli.Command { Name: "parameters", Usage: "The names of the script's parameters", }, + cli.StringFlag{ + Name: "labels", + Usage: "A list of comma separated label names to be associated with script", + }, }, }, { @@ -84,5 +94,47 @@ func SubCommands() []cli.Command { }, }, }, + { + Name: "add-label", + Usage: "This action assign a single label from a single labelable resource", + Action: cmd.LabelAdd, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "Script Id", + }, + cli.StringFlag{ + Name: "label", + Usage: "Label name", + }, + cli.StringFlag{ + Name: "resource_type", + Usage: "Resource Type", + Value: "script", + Hidden: true, + }, + }, + }, + { + Name: "remove-label", + Usage: "This action de-assign a single label from a single labelable resource", + Action: cmd.LabelRemove, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "Script Id", + }, + cli.StringFlag{ + Name: "label", + Usage: "Label name", + }, + cli.StringFlag{ + Name: "resource_type", + Usage: "Resource Type", + Value: "script", + Hidden: true, + }, + }, + }, } } diff --git a/blueprint/templates/subcommands.go b/blueprint/templates/subcommands.go index d3c632e..3c03c6c 100644 --- a/blueprint/templates/subcommands.go +++ b/blueprint/templates/subcommands.go @@ -11,6 +11,12 @@ func SubCommands() []cli.Command { Name: "list", Usage: "Lists all available templates", Action: cmd.TemplateList, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "labels", + Usage: "A list of comma separated label as a query filter", + }, + }, }, { Name: "show", @@ -44,6 +50,10 @@ func SubCommands() []cli.Command { Name: "configuration_attributes", Usage: "The attributes used to configure the services in the service_list", }, + cli.StringFlag{ + Name: "labels", + Usage: "A list of comma separated label names to be associated with template", + }, }, }, { @@ -201,5 +211,47 @@ func SubCommands() []cli.Command { }, }, }, + { + Name: "add-label", + Usage: "This action assign a single label from a single labelable resource", + Action: cmd.LabelAdd, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "Template Id", + }, + cli.StringFlag{ + Name: "label", + Usage: "Label name", + }, + cli.StringFlag{ + Name: "resource_type", + Usage: "Resource Type", + Value: "template", + Hidden: true, + }, + }, + }, + { + Name: "remove-label", + Usage: "This action de-assign a single label from a single labelable resource", + Action: cmd.LabelRemove, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "Template Id", + }, + cli.StringFlag{ + Name: "label", + Usage: "Label name", + }, + cli.StringFlag{ + Name: "resource_type", + Usage: "Resource Type", + Value: "template", + Hidden: true, + }, + }, + }, } } diff --git a/cloud/servers/subcommands.go b/cloud/servers/subcommands.go index aba1889..e419ad3 100644 --- a/cloud/servers/subcommands.go +++ b/cloud/servers/subcommands.go @@ -11,6 +11,12 @@ func SubCommands() []cli.Command { Name: "list", Usage: "Lists information about all the servers on this account.", Action: cmd.ServerList, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "labels", + Usage: "A list of comma separated label as a query filter", + }, + }, }, { Name: "show", @@ -52,6 +58,10 @@ func SubCommands() []cli.Command { Name: "cloud_account_id", Usage: "Identifier of the cloud account in which the server shall be registered", }, + cli.StringFlag{ + Name: "labels", + Usage: "A list of comma separated label names to be associated with server", + }, }, }, { @@ -162,5 +172,47 @@ func SubCommands() []cli.Command { }, }, }, + { + Name: "add-label", + Usage: "This action assign a single label from a single labelable resource", + Action: cmd.LabelAdd, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "Server Id", + }, + cli.StringFlag{ + Name: "label", + Usage: "Label name", + }, + cli.StringFlag{ + Name: "resource_type", + Usage: "Resource Type", + Value: "server", + Hidden: true, + }, + }, + }, + { + Name: "remove-label", + Usage: "This action de-assign a single label from a single labelable resource", + Action: cmd.LabelRemove, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "Server Id", + }, + cli.StringFlag{ + Name: "label", + Usage: "Label name", + }, + cli.StringFlag{ + Name: "resource_type", + Usage: "Resource Type", + Value: "server", + Hidden: true, + }, + }, + }, } } diff --git a/cloud/ssh_profiles/subcommands.go b/cloud/ssh_profiles/subcommands.go index ef5af9f..9b8eddb 100644 --- a/cloud/ssh_profiles/subcommands.go +++ b/cloud/ssh_profiles/subcommands.go @@ -11,6 +11,12 @@ func SubCommands() []cli.Command { Name: "list", Usage: "Lists all available SSH profiles.", Action: cmd.SSHProfileList, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "labels", + Usage: "A list of comma separated label as a query filter", + }, + }, }, { Name: "show", @@ -40,6 +46,10 @@ func SubCommands() []cli.Command { Name: "private_key", Usage: "Private key of the SSH profile", }, + cli.StringFlag{ + Name: "labels", + Usage: "A list of comma separated label names to be associated with SSH profile", + }, }, }, { @@ -76,5 +86,47 @@ func SubCommands() []cli.Command { }, }, }, + { + Name: "add-label", + Usage: "This action assign a single label from a single labelable resource", + Action: cmd.LabelAdd, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "SSH profile id", + }, + cli.StringFlag{ + Name: "label", + Usage: "Label name", + }, + cli.StringFlag{ + Name: "resource_type", + Usage: "Resource Type", + Value: "ssh_profile", + Hidden: true, + }, + }, + }, + { + Name: "remove-label", + Usage: "This action de-assign a single label from a single labelable resource", + Action: cmd.LabelRemove, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "SSH profile id", + }, + cli.StringFlag{ + Name: "label", + Usage: "Label name", + }, + cli.StringFlag{ + Name: "resource_type", + Usage: "Resource Type", + Value: "ssh_profile", + Hidden: true, + }, + }, + }, } } diff --git a/cmd/firewall_profiles_cmd.go b/cmd/firewall_profiles_cmd.go index 94e65f0..0652033 100644 --- a/cmd/firewall_profiles_cmd.go +++ b/cmd/firewall_profiles_cmd.go @@ -3,6 +3,7 @@ package cmd import ( "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/network" + "github.com/ingrammicro/concerto/api/types" "github.com/ingrammicro/concerto/utils" "github.com/ingrammicro/concerto/utils/format" ) @@ -37,6 +38,19 @@ func FirewallProfileList(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive firewallProfile data", err) } + + filteredResources, err := LabelFiltering(c, firewallProfiles) + if err != nil { + formatter.PrintFatal("Couldn't list firewall profiles filtered by labels", err) + } + if filteredResources != nil { + firewallProfiles = nil + for _, v := range *filteredResources { + firewallProfiles = append(firewallProfiles, v.(types.FirewallProfile)) + } + } + + LabelAssignNamesForIDs(c, firewallProfiles) if err = formatter.PrintList(firewallProfiles); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -53,6 +67,8 @@ func FirewallProfileShow(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive firewallProfile data", err) } + + LabelAssignNamesForIDs(c, firewallProfile) if err = formatter.PrintItem(*firewallProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -69,10 +85,25 @@ func FirewallProfileCreate(c *cli.Context) error { if err != nil { formatter.PrintFatal("Error parsing parameters", err) } - firewallProfile, err := firewallProfileSvc.CreateFirewallProfile(params) + + firewallProfileIn := map[string]interface{}{ + "name": c.String("name"), + "description": c.String("description"), + } + if c.String("rules") != "" { + firewallProfileIn["rules"] = (*params)["rules"] + } + if c.IsSet("labels") { + labelsIdsArr := LabelResolution(c, c.String("labels")) + firewallProfileIn["label_ids"] = labelsIdsArr + } + + firewallProfile, err := firewallProfileSvc.CreateFirewallProfile(&firewallProfileIn) if err != nil { formatter.PrintFatal("Couldn't create firewallProfile", err) } + + LabelAssignNamesForIDs(c, firewallProfile) if err = formatter.PrintItem(*firewallProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/labels_cmd.go b/cmd/labels_cmd.go new file mode 100644 index 0000000..5915e14 --- /dev/null +++ b/cmd/labels_cmd.go @@ -0,0 +1,255 @@ +package cmd + +import ( + "fmt" + "reflect" + "regexp" + "strings" + + "github.com/codegangsta/cli" + "github.com/ingrammicro/concerto/api/labels" + "github.com/ingrammicro/concerto/utils" + "github.com/ingrammicro/concerto/utils/format" +) + +// WireUpLabel prepares common resources to send request to Concerto API +func WireUpLabel(c *cli.Context) (ds *labels.LabelService, f format.Formatter) { + + f = format.GetFormatter() + + config, err := utils.GetConcertoConfig() + if err != nil { + f.PrintFatal("Couldn't wire up config", err) + } + hcs, err := utils.NewHTTPConcertoService(config) + if err != nil { + f.PrintFatal("Couldn't wire up concerto service", err) + } + ds, err = labels.NewLabelService(hcs) + if err != nil { + f.PrintFatal("Couldn't wire up label service", err) + } + + return ds, f +} + +// LabelList subcommand function +func LabelList(c *cli.Context) error { + debugCmdFuncInfo(c) + + labelsSvc, formatter := WireUpLabel(c) + labels, err := labelsSvc.GetLabelList() + if err != nil { + formatter.PrintFatal("Couldn't receive labels data", err) + } + + if err = formatter.PrintList(labels); err != nil { + formatter.PrintFatal("Couldn't print/format result", err) + } + return nil +} + +// LabelCreate subcommand function +func LabelCreate(c *cli.Context) error { + debugCmdFuncInfo(c) + + labelsSvc, formatter := WireUpLabel(c) + checkRequiredFlags(c, []string{"name", "resource_type"}, formatter) + label, err := labelsSvc.CreateLabel(utils.FlagConvertParams(c)) + if err != nil { + formatter.PrintFatal("Couldn't create label", err) + } + if err = formatter.PrintItem(*label); err != nil { + formatter.PrintFatal("Couldn't print/format result", err) + } + return nil +} + +// LabelFiltering subcommand function receives an interface representing a collection of labelable resources (Server, Template, ...) +// Evaluates the matching of assigned labels with the labels requested for filtering. +func LabelFiltering(c *cli.Context, items interface{}) (*[]interface{}, error) { + debugCmdFuncInfo(c) + + if c.String("labels") != "" { + its := reflect.ValueOf(items) + if its.Type().Kind() != reflect.Slice { + return nil, fmt.Errorf("Cannot process label filtering. Slice expected") + } + + // evaluates labels + _, formatter := WireUpLabel(c) + labelNamesIn := LabelsUnifyInputNames(c.String("labels"), formatter) + + // Load Labels mapping ID <-> NAME + _, labelsMapIDToName := LabelLoadsMapping(c) + + var filteredResources []interface{} + var tmpLabelNames []string + // per resource (Server, Template, ...) + for i := 0; i < its.Len(); i++ { + tmpLabelNames = nil + labelIDs := reflect.ValueOf(its.Index(i).FieldByName("LabelIDs").Interface()) + if len := labelIDs.Len(); len > 0 { + for j := 0; j < len; j++ { + tmpLabelNames = append(tmpLabelNames, labelsMapIDToName[labelIDs.Index(j).String()]) + } + } + // checks whether received labels match for resources labels + if utils.Subset(labelNamesIn, tmpLabelNames) { + filteredResources = append(filteredResources, its.Index(i).Interface()) + } + } + return &filteredResources, nil + } + return nil, nil +} + +// LabelAssignNamesForIDs subcommand function receives an interface representing labelable resources (Server, Template, ...) +// Resolves the Labels names associated to a each resource from given Labels ids, loading object with respective labels names +func LabelAssignNamesForIDs(c *cli.Context, items interface{}) { + debugCmdFuncInfo(c) + + var tmpLabelNames []string + + // Load Labels mapping ID <-> NAME + _, labelsMapIDToName := LabelLoadsMapping(c) + + its := reflect.ValueOf(items) + if its.Type().Kind() == reflect.Slice { // resources collection + // per resource (Server, Template, ...) + for i := 0; i < its.Len(); i++ { + tmpLabelNames = nil + labelIDs := reflect.ValueOf(its.Index(i).FieldByName("LabelIDs").Interface()) + if len := labelIDs.Len(); len > 0 { + for j := 0; j < len; j++ { + tmpLabelNames = append(tmpLabelNames, labelsMapIDToName[labelIDs.Index(j).String()]) + } + } + its.Index(i).FieldByName("Labels").Set(reflect.ValueOf(tmpLabelNames)) + } + } else if its.Type().Kind() == reflect.Ptr { // resource + labelIDs := reflect.Indirect(its).FieldByName("LabelIDs") + if len := labelIDs.Len(); len > 0 { + for j := 0; j < len; j++ { + tmpLabelNames = append(tmpLabelNames, labelsMapIDToName[labelIDs.Index(j).String()]) + } + } + reflect.Indirect(its).FieldByName("Labels").Set(reflect.ValueOf(tmpLabelNames)) + } +} + +// LabelLoadsMapping subcommand function retrieves the current label list in IMCO; then prepares two mapping structures (Name <-> ID and ID <-> Name) +func LabelLoadsMapping(c *cli.Context) (map[string]string, map[string]string) { + debugCmdFuncInfo(c) + + labelsSvc, formatter := WireUpLabel(c) + labels, err := labelsSvc.GetLabelList() + if err != nil { + formatter.PrintFatal("Couldn't receive labels data", err) + } + + labelsMapNameToID := make(map[string]string) + labelsMapIDToName := make(map[string]string) + + for _, label := range labels { + labelsMapNameToID[label.Name] = label.ID + labelsMapIDToName[label.ID] = label.Name + } + return labelsMapNameToID, labelsMapIDToName +} + +// LabelsUnifyInputNames subcommand function evaluates the received labels names (comma separated string). +// Validates, remove duplicates and resolves a slice with unique label names. +func LabelsUnifyInputNames(labelsNames string, formatter format.Formatter) []string { + labelNamesIn := utils.RemoveDuplicates(strings.Split(labelsNames, ",")) + for _, c := range labelNamesIn { + matched := regexp.MustCompile(`^[A-Za-z0-9 .\s_-]+$`).MatchString(c) + if !matched { + formatter.PrintFatal("Invalid label name ", fmt.Errorf("Invalid label format: %v (Labels would be indicated with their name, which must satisfy to be composed of spaces, underscores, dots, dashes and/or lower/upper -case alphanumeric characters-)", c)) + } + } + return labelNamesIn +} + +// LabelResolution subcommand function retrieves a labels map(Name<->ID) based on label names received to be procesed. +// The function evaluates the received labels names (comma separated string); with them, solves the assigned IDs for the given labels names. +// If the label name is not avaiable in IMCO yet, it is created. +func LabelResolution(c *cli.Context, labelsNames string) []string { + debugCmdFuncInfo(c) + + labelsSvc, formatter := WireUpLabel(c) + labelNamesIn := LabelsUnifyInputNames(labelsNames, formatter) + labelsMapNameToID, _ := LabelLoadsMapping(c) + + // Obtain output mapped labels Name<->ID; currenlty in IMCO platform as well as if creation is required + labelsOutMap := make(map[string]string) + for _, name := range labelNamesIn { + // check if the label already exists in IMCO, creates it if it does not exist + if labelsMapNameToID[name] == "" { + labelPayload := make(map[string]interface{}) + labelPayload["name"] = name + newLabel, err := labelsSvc.CreateLabel(&labelPayload) + if err != nil { + formatter.PrintFatal("Couldn't create label", err) + } + labelsOutMap[name] = newLabel.ID + } else { + labelsOutMap[name] = labelsMapNameToID[name] + } + } + labelsIdsArr := make([]string, 0) + for _, mp := range labelsOutMap { + labelsIdsArr = append(labelsIdsArr, mp) + } + return labelsIdsArr +} + +// LabelAdd subcommand function assigns a single label from a single labelable resource +func LabelAdd(c *cli.Context) error { + debugCmdFuncInfo(c) + + labelsSvc, formatter := WireUpLabel(c) + checkRequiredFlags(c, []string{"id", "label"}, formatter) + + labelsIdsArr := LabelResolution(c, c.String("label")) + if len(labelsIdsArr) > 1 { + formatter.PrintFatal("Too many label names. Please, Use only one label name", fmt.Errorf("Invalid parameter: %v - %v", c.String("label"), labelsIdsArr)) + } + labelID := labelsIdsArr[0] + + resData := make(map[string]string) + resData["id"] = c.String("id") + resData["resource_type"] = c.String("resource_type") + resourcesData := make([]interface{}, 0, 1) + resourcesData = append(resourcesData, resData) + + labelIn := map[string]interface{}{ + "resources": resourcesData, + } + + labeledResources, err := labelsSvc.AddLabel(&labelIn, labelID) + if err != nil { + formatter.PrintFatal("Couldn't add label data", err) + } + if err = formatter.PrintList(labeledResources); err != nil { + formatter.PrintFatal("Couldn't print/format result", err) + } + return nil +} + +// LabelRemove subcommand function de-assigns a single label from a single labelable resource +func LabelRemove(c *cli.Context) error { + debugCmdFuncInfo(c) + + labelsSvc, formatter := WireUpLabel(c) + checkRequiredFlags(c, []string{"id", "label"}, formatter) + + labelsMapNameToID, _ := LabelLoadsMapping(c) + labelID := labelsMapNameToID[c.String("label")] + + err := labelsSvc.RemoveLabel(labelID, c.String("resource_type"), c.String("id")) + if err != nil { + formatter.PrintFatal("Couldn't remove label", err) + } + return nil +} diff --git a/cmd/scripts_cmd.go b/cmd/scripts_cmd.go index 2568f03..763f6b9 100644 --- a/cmd/scripts_cmd.go +++ b/cmd/scripts_cmd.go @@ -1,8 +1,11 @@ package cmd import ( + "strings" + "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/blueprint" + "github.com/ingrammicro/concerto/api/types" "github.com/ingrammicro/concerto/utils" "github.com/ingrammicro/concerto/utils/format" ) @@ -37,6 +40,19 @@ func ScriptsList(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive script data", err) } + + filteredResources, err := LabelFiltering(c, scripts) + if err != nil { + formatter.PrintFatal("Couldn't list scripts filtered by labels", err) + } + if filteredResources != nil { + scripts = nil + for _, v := range *filteredResources { + scripts = append(scripts, v.(types.Script)) + } + } + + LabelAssignNamesForIDs(c, scripts) if err = formatter.PrintList(scripts); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -53,6 +69,8 @@ func ScriptShow(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive script data", err) } + + LabelAssignNamesForIDs(c, script) if err = formatter.PrintItem(*script); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -65,10 +83,25 @@ func ScriptCreate(c *cli.Context) error { scriptSvc, formatter := WireUpScript(c) checkRequiredFlags(c, []string{"name", "description", "code"}, formatter) - script, err := scriptSvc.CreateScript(utils.FlagConvertParams(c)) + scriptIn := map[string]interface{}{ + "name": c.String("name"), + "description": c.String("description"), + "code": c.String("code"), + } + if c.String("parameters") != "" { + scriptIn["parameters"] = strings.Split(c.String("parameters"), ",") + } + if c.IsSet("labels") { + labelsIdsArr := LabelResolution(c, c.String("labels")) + scriptIn["label_ids"] = labelsIdsArr + } + + script, err := scriptSvc.CreateScript(&scriptIn) if err != nil { formatter.PrintFatal("Couldn't create script", err) } + + LabelAssignNamesForIDs(c, script) if err = formatter.PrintItem(*script); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/servers_cmd.go b/cmd/servers_cmd.go index 5041e8f..b6fa420 100644 --- a/cmd/servers_cmd.go +++ b/cmd/servers_cmd.go @@ -3,6 +3,7 @@ package cmd import ( "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/cloud" + "github.com/ingrammicro/concerto/api/types" "github.com/ingrammicro/concerto/utils" "github.com/ingrammicro/concerto/utils/format" ) @@ -37,6 +38,19 @@ func ServerList(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive server data", err) } + + filteredResources, err := LabelFiltering(c, servers) + if err != nil { + formatter.PrintFatal("Couldn't list servers filtered by labels", err) + } + if filteredResources != nil { + servers = nil + for _, v := range *filteredResources { + servers = append(servers, v.(types.Server)) + } + } + + LabelAssignNamesForIDs(c, servers) if err = formatter.PrintList(servers); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -53,6 +67,8 @@ func ServerShow(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive server data", err) } + + LabelAssignNamesForIDs(c, server) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -65,10 +81,26 @@ func ServerCreate(c *cli.Context) error { serverSvc, formatter := WireUpServer(c) checkRequiredFlags(c, []string{"name", "ssh_profile_id", "firewall_profile_id", "template_id", "server_plan_id", "cloud_account_id"}, formatter) - server, err := serverSvc.CreateServer(utils.FlagConvertParams(c)) + serverIn := map[string]interface{}{ + "name": c.String("name"), + "ssh_profile_id": c.String("ssh_profile_id"), + "firewall_profile_id": c.String("firewall_profile_id"), + "template_id": c.String("template_id"), + "server_plan_id": c.String("server_plan_id"), + "cloud_account_id": c.String("cloud_account_id"), + } + + if c.IsSet("labels") { + labelsIdsArr := LabelResolution(c, c.String("labels")) + serverIn["label_ids"] = labelsIdsArr + } + + server, err := serverSvc.CreateServer(&serverIn) if err != nil { formatter.PrintFatal("Couldn't create server", err) } + + LabelAssignNamesForIDs(c, server) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/ssh_profiles_cmd.go b/cmd/ssh_profiles_cmd.go index 4010b45..4497e6c 100644 --- a/cmd/ssh_profiles_cmd.go +++ b/cmd/ssh_profiles_cmd.go @@ -3,6 +3,7 @@ package cmd import ( "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/cloud" + "github.com/ingrammicro/concerto/api/types" "github.com/ingrammicro/concerto/utils" "github.com/ingrammicro/concerto/utils/format" ) @@ -37,6 +38,19 @@ func SSHProfileList(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive sshProfile data", err) } + + filteredResources, err := LabelFiltering(c, sshProfiles) + if err != nil { + formatter.PrintFatal("Couldn't list SSH profiles filtered by labels", err) + } + if filteredResources != nil { + sshProfiles = nil + for _, v := range *filteredResources { + sshProfiles = append(sshProfiles, v.(types.SSHProfile)) + } + } + + LabelAssignNamesForIDs(c, sshProfiles) if err = formatter.PrintList(sshProfiles); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -53,6 +67,8 @@ func SSHProfileShow(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive sshProfile data", err) } + + LabelAssignNamesForIDs(c, sshProfile) if err = formatter.PrintItem(*sshProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -65,10 +81,24 @@ func SSHProfileCreate(c *cli.Context) error { sshProfileSvc, formatter := WireUpSSHProfile(c) checkRequiredFlags(c, []string{"name", "public_key"}, formatter) - sshProfile, err := sshProfileSvc.CreateSSHProfile(utils.FlagConvertParams(c)) + sshProfileIn := map[string]interface{}{ + "name": c.String("name"), + "public_key": c.String("public_key"), + } + if c.String("private_key") != "" { + sshProfileIn["private_key"] = c.String("private_key") + } + if c.IsSet("labels") { + labelsIdsArr := LabelResolution(c, c.String("labels")) + sshProfileIn["label_ids"] = labelsIdsArr + } + + sshProfile, err := sshProfileSvc.CreateSSHProfile(&sshProfileIn) if err != nil { formatter.PrintFatal("Couldn't create sshProfile", err) } + + LabelAssignNamesForIDs(c, sshProfile) if err = formatter.PrintItem(*sshProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/template_cmd.go b/cmd/template_cmd.go index 9ef3ccd..bcb497e 100644 --- a/cmd/template_cmd.go +++ b/cmd/template_cmd.go @@ -3,6 +3,7 @@ package cmd import ( "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/blueprint" + "github.com/ingrammicro/concerto/api/types" "github.com/ingrammicro/concerto/utils" "github.com/ingrammicro/concerto/utils/format" ) @@ -37,6 +38,19 @@ func TemplateList(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive template data", err) } + + filteredResources, err := LabelFiltering(c, templates) + if err != nil { + formatter.PrintFatal("Couldn't list templates filtered by labels", err) + } + if filteredResources != nil { + templates = nil + for _, v := range *filteredResources { + templates = append(templates, v.(types.Template)) + } + } + + LabelAssignNamesForIDs(c, templates) if err = formatter.PrintList(templates); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -53,6 +67,8 @@ func TemplateShow(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive template data", err) } + + LabelAssignNamesForIDs(c, template) if err = formatter.PrintItem(*template); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -65,17 +81,30 @@ func TemplateCreate(c *cli.Context) error { templateSvc, formatter := WireUpTemplate(c) checkRequiredFlags(c, []string{"name", "generic_image_id"}, formatter) - // parse json parameter values params, err := utils.FlagConvertParamsJSON(c, []string{"service_list", "configuration_attributes"}) if err != nil { formatter.PrintFatal("Error parsing parameters", err) } - template, err := templateSvc.CreateTemplate(params) + templateIn := map[string]interface{}{ + "name": c.String("name"), + "generic_image_id": c.String("generic_image_id"), + "service_list": (*params)["service_list"], + "configuration_attributes": (*params)["configuration_attributes"], + } + + if c.IsSet("labels") { + labelsIdsArr := LabelResolution(c, c.String("labels")) + templateIn["label_ids"] = labelsIdsArr + } + + template, err := templateSvc.CreateTemplate(&templateIn) if err != nil { formatter.PrintFatal("Couldn't create template", err) } + + LabelAssignNamesForIDs(c, template) if err = formatter.PrintItem(*template); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/labels/subcommands.go b/labels/subcommands.go new file mode 100644 index 0000000..e86c699 --- /dev/null +++ b/labels/subcommands.go @@ -0,0 +1,16 @@ +package labels + +import ( + "github.com/codegangsta/cli" + "github.com/ingrammicro/concerto/cmd" +) + +func SubCommands() []cli.Command { + return []cli.Command{ + { + Name: "list", + Usage: "Lists the current labels existing in the platform for the user", + Action: cmd.LabelList, + }, + } +} diff --git a/main.go b/main.go index bbe80ff..cd170f5 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,7 @@ import ( "github.com/ingrammicro/concerto/converge" "github.com/ingrammicro/concerto/dispatcher" "github.com/ingrammicro/concerto/firewall" + "github.com/ingrammicro/concerto/labels" "github.com/ingrammicro/concerto/network/firewall_profiles" "github.com/ingrammicro/concerto/settings/cloud_accounts" "github.com/ingrammicro/concerto/setup" @@ -247,6 +248,14 @@ var ClientCommands = []cli.Command{ WizardCommands, ), }, + { + Name: "labels", + ShortName: "lbl", + Usage: "Provides information about labels", + Subcommands: append( + labels.SubCommands(), + ), + }, } var appFlags = []cli.Flag{ diff --git a/network/firewall_profiles/subcommands.go b/network/firewall_profiles/subcommands.go index 23ff6e1..e2519a7 100644 --- a/network/firewall_profiles/subcommands.go +++ b/network/firewall_profiles/subcommands.go @@ -11,6 +11,12 @@ func SubCommands() []cli.Command { Name: "list", Usage: "Lists all existing firewall profiles", Action: cmd.FirewallProfileList, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "labels", + Usage: "A list of comma separated label as a query filter", + }, + }, }, { Name: "show", @@ -40,6 +46,10 @@ func SubCommands() []cli.Command { Name: "rules", Usage: "Set of rules of the firewall profile", }, + cli.StringFlag{ + Name: "labels", + Usage: "A list of comma separated label names to be associated with firewall profile", + }, }, }, { @@ -76,5 +86,47 @@ func SubCommands() []cli.Command { }, }, }, + { + Name: "add-label", + Usage: "This action assign a single label from a single labelable resource", + Action: cmd.LabelAdd, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "Firewall profile Id", + }, + cli.StringFlag{ + Name: "label", + Usage: "Label name", + }, + cli.StringFlag{ + Name: "resource_type", + Usage: "Resource Type", + Value: "firewall_profile", + Hidden: true, + }, + }, + }, + { + Name: "remove-label", + Usage: "This action de-assign a single label from a single labelable resource", + Action: cmd.LabelRemove, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "Firewall profile Id", + }, + cli.StringFlag{ + Name: "label", + Usage: "Label name", + }, + cli.StringFlag{ + Name: "resource_type", + Usage: "Resource Type", + Value: "firewall_profile", + Hidden: true, + }, + }, + }, } } diff --git a/testdata/labels_data.go b/testdata/labels_data.go new file mode 100644 index 0000000..f51724c --- /dev/null +++ b/testdata/labels_data.go @@ -0,0 +1,24 @@ +package testdata + +import ( + "github.com/ingrammicro/concerto/api/types" +) + +// GetLabelData loads test data +func GetLabelData() *[]types.Label { + + testLabels := []types.Label{ + { + ID: "fakeID0", + Name: "fakeName0", + ResourceType: "label", + }, + { + ID: "fakeID1", + Name: "fakeName1", + ResourceType: "label", + }, + } + + return &testLabels +} diff --git a/utils/format/textformatter.go b/utils/format/textformatter.go index 3408de7..67f5680 100644 --- a/utils/format/textformatter.go +++ b/utils/format/textformatter.go @@ -35,14 +35,25 @@ func (f *TextFormatter) PrintItem(item interface{}) error { w := tabwriter.NewWriter(f.output, 15, 1, 3, ' ', 0) for i := 0; i < nf; i++ { - // TODO not the best way to use reflection. Check this later - switch it.Field(i).Type().String() { - case "json.RawMessage": - fmt.Fprintf(w, "%s:\t%s\n", it.Type().Field(i).Tag.Get("header"), it.Field(i).Interface()) - case "*json.RawMessage": - fmt.Fprintf(w, "%s:\t%s\n", it.Type().Field(i).Tag.Get("header"), it.Field(i).Elem()) - default: - fmt.Fprintf(w, "%s:\t%+v\n", it.Type().Field(i).Tag.Get("header"), it.Field(i).Interface()) + // hide fields + bShow := true + showTags := strings.Split(it.Type().Field(i).Tag.Get("show"), ",") + for _, showTag := range showTags { + if showTag == "noshow" { + bShow = false + } + } + + if bShow { + // TODO not the best way to use reflection. Check this later + switch it.Field(i).Type().String() { + case "json.RawMessage": + fmt.Fprintf(w, "%s:\t%s\n", it.Type().Field(i).Tag.Get("header"), it.Field(i).Interface()) + case "*json.RawMessage": + fmt.Fprintf(w, "%s:\t%s\n", it.Type().Field(i).Tag.Get("header"), it.Field(i).Elem()) + default: + fmt.Fprintf(w, "%s:\t%+v\n", it.Type().Field(i).Tag.Get("header"), it.Field(i).Interface()) + } } } fmt.Fprintln(w) diff --git a/utils/utils.go b/utils/utils.go index d4de652..8b8b2a6 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -167,6 +167,7 @@ func CheckRequiredFlags(c *cli.Context, flags []string) { } } +// RandomString generates a random string from lowercase letters and numbers func RandomString(strlen int) string { r := rand.New(rand.NewSource(time.Now().UnixNano())) const chars = "abcdefghijklmnopqrstuvwxyz0123456789" @@ -176,3 +177,44 @@ func RandomString(strlen int) string { } return string(result) } + +// RemoveDuplicates returns the slice removing duplicates if exist +func RemoveDuplicates(elements []string) []string { + encountered := map[string]bool{} + + // Create a map of all unique elements. + for v := range elements { + encountered[elements[v]] = true + } + + // Place all keys from the map into a slice. + result := []string{} + for key := range encountered { + result = append(result, key) + } + return result +} + +// Contains evaluates whether s contains x. +func Contains(s []string, x string) bool { + for _, n := range s { + if x == n { + return true + } + } + return false +} + +// Subset returns true if the first slice is completely contained in the second slice. +// There must be at least the same number of duplicate values in second as there are in first. +func Subset(s1, s2 []string) bool { + if len(s1) > len(s2) { + return false + } + for _, e := range s1 { + if !Contains(s2, e) { + return false + } + } + return true +} From af9b96046837cea6d71a70daf17b452ce2dd4116 Mon Sep 17 00:00:00 2001 From: Pablo Cantera Date: Thu, 19 Jul 2018 13:52:50 +0200 Subject: [PATCH 2/9] Added test cases (issue #77) --- api/labels/labels_api_mocked.go | 250 +++++++++++++++++++++++++++++--- api/labels/labels_api_test.go | 25 +++- testdata/labels_data.go | 40 +++++ 3 files changed, 292 insertions(+), 23 deletions(-) diff --git a/api/labels/labels_api_mocked.go b/api/labels/labels_api_mocked.go index bf2b615..ec0d5e0 100644 --- a/api/labels/labels_api_mocked.go +++ b/api/labels/labels_api_mocked.go @@ -34,6 +34,30 @@ func GetLabelListMocked(t *testing.T, labelsIn *[]types.Label) *[]types.Label { return &labelsOut } +// GetLabelListMockedWithNamespace test mocked function +func GetLabelListMockedWithNamespace(t *testing.T, labelsIn *[]types.Label) *[]types.Label { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // to json + dIn, err := json.Marshal(labelsIn) + assert.Nil(err, "Label test data corrupted") + + // call service + cs.On("Get", "/v1/labels").Return(dIn, 200, nil) + labelsOut, err := ds.GetLabelList() + assert.Nil(err, "Error getting labels list") + assert.NotEqual(*labelsIn, labelsOut, "GetLabelList returned labels with Namespaces") + + return &labelsOut +} + // GetLabelListFailErrMocked test mocked function func GetLabelListFailErrMocked(t *testing.T, labelsIn *[]types.Label) *[]types.Label { @@ -199,31 +223,215 @@ func CreateLabelFailStatusMocked(t *testing.T, labelIn *types.Label) *types.Labe return labelOut } -// // CreateLabelFailJSONMocked test mocked function -// func CreateLabelFailJSONMocked(t *testing.T, labelIn *types.Label) *types.Label { +// CreateLabelFailJSONMocked test mocked function +func CreateLabelFailJSONMocked(t *testing.T, labelIn *types.Label) *types.Label { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // convertMap + mapIn, err := utils.ItemConvertParams(*labelIn) + assert.Nil(err, "Label test data corrupted") + + // wrong json + dIn := []byte{10, 20, 30} + + // call service + cs.On("Post", "/v1/labels/", mapIn).Return(dIn, 200, nil) + labelOut, err := ds.CreateLabel(mapIn) + + assert.NotNil(err, "We are expecting a marshalling error") + assert.Nil(labelOut, "Expecting nil output") + assert.Contains(err.Error(), "invalid character", "Error message should include the string 'invalid character'") + + return labelOut +} + +// AddLabelMocked test mocked function +func AddLabelMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResources) []types.LabeledResources { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // convertMap + mapIn, err := utils.ItemConvertParams(*labelIn) + assert.Nil(err, "Label test data corrupted") + + // to json + dOut, err := json.Marshal(labeledResourcesOut) + assert.Nil(err, "Label test data corrupted") + + // call service + cs.On("Post", fmt.Sprintf("/v1/labels/%s/resources", labelIn.ID), mapIn).Return(dOut, 200, nil) + labeledOut, err := ds.AddLabel(mapIn, labelIn.ID) + assert.Nil(err, "Error creating label") + assert.Equal(labeledOut, labeledResourcesOut, "CreateLabel returned invalid labeled resources") + + return labeledResourcesOut +} + +// AddLabelFailErrMocked test mocked function +func AddLabelFailErrMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResources) []types.LabeledResources { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // convertMap + mapIn, err := utils.ItemConvertParams(*labelIn) + assert.Nil(err, "Label test data corrupted") + + // to json + dOut, err := json.Marshal(labeledResourcesOut) + assert.Nil(err, "Label test data corrupted") + + // call service + cs.On("Post", fmt.Sprintf("/v1/labels/%s/resources", labelIn.ID), mapIn).Return(dOut, 200, fmt.Errorf("Mocked error")) + labeledOut, err := ds.AddLabel(mapIn, labelIn.ID) + assert.NotNil(err, "We are expecting an error") + assert.Nil(labeledOut, "Expecting nil output") + assert.Equal(err.Error(), "Mocked error", "Error should be 'Mocked error'") + + return labeledResourcesOut +} + +// AddLabelFailStatusMocked test mocked function +func AddLabelFailStatusMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResources) []types.LabeledResources { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // convertMap + mapIn, err := utils.ItemConvertParams(*labelIn) + assert.Nil(err, "Label test data corrupted") + + // to json + dOut, err := json.Marshal(labeledResourcesOut) + assert.Nil(err, "Label test data corrupted") + + // call service + cs.On("Post", fmt.Sprintf("/v1/labels/%s/resources", labelIn.ID), mapIn).Return(dOut, 404, nil) + labeledOut, err := ds.AddLabel(mapIn, labelIn.ID) + assert.NotNil(err, "We are expecting an status code error") + assert.Nil(labeledOut, "Expecting nil output") + assert.Contains(err.Error(), "404", "Error should contain http code 404") + + return labeledResourcesOut +} + +// AddLabelFailJSONMocked test mocked function +func AddLabelFailJSONMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResources) []types.LabeledResources { + + assert := assert.New(t) -// assert := assert.New(t) + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // convertMap + mapIn, err := utils.ItemConvertParams(*labelIn) + assert.Nil(err, "Label test data corrupted") -// // wire up -// cs := &utils.MockConcertoService{} -// ds, err := NewLabelService(cs) -// assert.Nil(err, "Couldn't load label service") -// assert.NotNil(ds, "Label service not instanced") + // wrong json + dOut := []byte{10, 20, 30} -// // convertMap -// mapIn, err := utils.ItemConvertParams(*labelIn) -// assert.Nil(err, "Label test data corrupted") + // call service + cs.On("Post", fmt.Sprintf("/v1/labels/%s/resources", labelIn.ID), mapIn).Return(dOut, 200, nil) + labeledOut, err := ds.AddLabel(mapIn, labelIn.ID) + assert.NotNil(err, "We are expecting a marshalling error") + assert.Nil(labeledOut, "Expecting nil output") + assert.Contains(err.Error(), "invalid character", "Error message should include the string 'invalid character'") -// // wrong json -// dIn := []byte{10, 20, 30} + return labeledResourcesOut +} -// // call service -// cs.On("Post", "/v1/cloud/servers/", mapIn).Return(dIn, 200, nil) -// labelOut, err := ds.CreateLabel(mapIn) +// RemoveLabelMocked test mocked function +func RemoveLabelMocked(t *testing.T, labelIn *types.Label) { -// assert.NotNil(err, "We are expecting a marshalling error") -// assert.Nil(labelOut, "Expecting nil output") -// assert.Contains(err.Error(), "invalid character", "Error message should include the string 'invalid character'") + assert := assert.New(t) -// return labelOut -// } + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // to json + dIn, err := json.Marshal(labelIn) + assert.Nil(err, "Label test data corrupted") + + // call service + resourceID := "5b5074735f7c880ad9c6bbce" + cs.On("Delete", fmt.Sprintf("v1/labels/%s/resources/%s/%s", labelIn.ID, labelIn.ResourceType, resourceID)).Return(dIn, 204, nil) + err = ds.RemoveLabel(labelIn.ID, labelIn.ResourceType, resourceID) + assert.Nil(err, "Error removing label") +} + +// RemoveLabelFailErrMocked test mocked function +func RemoveLabelFailErrMocked(t *testing.T, labelIn *types.Label) { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // to json + dIn, err := json.Marshal(labelIn) + assert.Nil(err, "Label test data corrupted") + + // call service + resourceID := "5b5074735f7c880ad9c6bbce" + cs.On("Delete", fmt.Sprintf("v1/labels/%s/resources/%s/%s", labelIn.ID, labelIn.ResourceType, resourceID)).Return(dIn, 204, fmt.Errorf("Mocked error")) + err = ds.RemoveLabel(labelIn.ID, labelIn.ResourceType, resourceID) + + assert.NotNil(err, "We are expecting an error") + assert.Equal(err.Error(), "Mocked error", "Error should be 'Mocked error'") +} + +// RemoveLabelFailStatusMocked test mocked function +func RemoveLabelFailStatusMocked(t *testing.T, labelIn *types.Label) { + + assert := assert.New(t) + + // wire up + cs := &utils.MockConcertoService{} + ds, err := NewLabelService(cs) + assert.Nil(err, "Couldn't load label service") + assert.NotNil(ds, "Label service not instanced") + + // to json + dIn, err := json.Marshal(labelIn) + assert.Nil(err, "Label test data corrupted") + + // call service + resourceID := "5b5074735f7c880ad9c6bbce" + cs.On("Delete", fmt.Sprintf("v1/labels/%s/resources/%s/%s", labelIn.ID, labelIn.ResourceType, resourceID)).Return(dIn, 404, nil) + err = ds.RemoveLabel(labelIn.ID, labelIn.ResourceType, resourceID) + + assert.NotNil(err, "We are expecting an status code error") + assert.Contains(err.Error(), "404", "Error should contain http code 404") +} diff --git a/api/labels/labels_api_test.go b/api/labels/labels_api_test.go index 8999aa9..f14e715 100644 --- a/api/labels/labels_api_test.go +++ b/api/labels/labels_api_test.go @@ -14,9 +14,10 @@ func TestNewLabelServiceNil(t *testing.T) { assert.NotNil(err, "Uninitialized service should return error") } -func TestGetCloudProviderList(t *testing.T) { +func TestGetLabelsList(t *testing.T) { labelsIn := testdata.GetLabelData() GetLabelListMocked(t, labelsIn) + GetLabelListMockedWithNamespace(t, testdata.GetLabelWithNamespaceData()) GetLabelListFailErrMocked(t, labelsIn) GetLabelListFailStatusMocked(t, labelsIn) GetLabelListFailJSONMocked(t, labelsIn) @@ -28,6 +29,26 @@ func TestCreateLabel(t *testing.T) { CreateLabelMocked(t, &labelIn) CreateLabelFailErrMocked(t, &labelIn) CreateLabelFailStatusMocked(t, &labelIn) - //CreateLabelFailJSONMocked(t, &labelIn) + CreateLabelFailJSONMocked(t, &labelIn) + } +} + +func TestAddLabel(t *testing.T) { + labelsIn := testdata.GetLabelData() + labeledResourcesOut := testdata.GetLabeledResourcesData() + for _, labelIn := range *labelsIn { + AddLabelMocked(t, &labelIn, *labeledResourcesOut) + AddLabelFailErrMocked(t, &labelIn, *labeledResourcesOut) + AddLabelFailStatusMocked(t, &labelIn, *labeledResourcesOut) + AddLabelFailJSONMocked(t, &labelIn, *labeledResourcesOut) + } +} + +func TestRemoveLabel(t *testing.T) { + labelsIn := testdata.GetLabelData() + for _, labelIn := range *labelsIn { + RemoveLabelMocked(t, &labelIn) + RemoveLabelFailErrMocked(t, &labelIn) + RemoveLabelFailStatusMocked(t, &labelIn) } } diff --git a/testdata/labels_data.go b/testdata/labels_data.go index f51724c..0b5af93 100644 --- a/testdata/labels_data.go +++ b/testdata/labels_data.go @@ -22,3 +22,43 @@ func GetLabelData() *[]types.Label { return &testLabels } + +// GetLabelWithNamespaceData loads test data +func GetLabelWithNamespaceData() *[]types.Label { + + testLabels := []types.Label{ + { + ID: "fakeID0", + Name: "fakeName0", + ResourceType: "label", + Namespace: "fakeNamespace0", + Value: "fakeValue0", + }, + { + ID: "fakeID1", + Name: "fakeName1", + ResourceType: "label", + Namespace: "fakeNamespace1", + Value: "fakeValue1", + }, + } + + return &testLabels +} + +// GetLabeledResourcesData loads test data +func GetLabeledResourcesData() *[]types.LabeledResources { + + testLabeledResources := []types.LabeledResources{ + { + ID: "fakeID0", + ResourceType: "server", + }, + { + ID: "fakeID1", + ResourceType: "template", + }, + } + + return &testLabeledResources +} From 5a442b7f29b80734bdf7388eb6989d871d0241a3 Mon Sep 17 00:00:00 2001 From: Pablo Cantera Date: Fri, 20 Jul 2018 12:57:54 +0200 Subject: [PATCH 3/9] Populated label names (issue #77) --- cmd/firewall_profiles_cmd.go | 2 ++ cmd/scripts_cmd.go | 2 ++ cmd/servers_cmd.go | 10 ++++++++++ cmd/ssh_profiles_cmd.go | 2 ++ cmd/template_cmd.go | 2 ++ 5 files changed, 18 insertions(+) diff --git a/cmd/firewall_profiles_cmd.go b/cmd/firewall_profiles_cmd.go index 0652033..569bbea 100644 --- a/cmd/firewall_profiles_cmd.go +++ b/cmd/firewall_profiles_cmd.go @@ -124,6 +124,8 @@ func FirewallProfileUpdate(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't update firewallProfile", err) } + + LabelAssignNamesForIDs(c, firewallProfile) if err = formatter.PrintItem(*firewallProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/scripts_cmd.go b/cmd/scripts_cmd.go index 763f6b9..83f610a 100644 --- a/cmd/scripts_cmd.go +++ b/cmd/scripts_cmd.go @@ -118,6 +118,8 @@ func ScriptUpdate(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't update script", err) } + + LabelAssignNamesForIDs(c, script) if err = formatter.PrintItem(*script); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/servers_cmd.go b/cmd/servers_cmd.go index b6fa420..8bd30d2 100644 --- a/cmd/servers_cmd.go +++ b/cmd/servers_cmd.go @@ -117,6 +117,8 @@ func ServerUpdate(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't update server", err) } + + LabelAssignNamesForIDs(c, server) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -133,6 +135,8 @@ func ServerBoot(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't boot server", err) } + + LabelAssignNamesForIDs(c, server) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -149,6 +153,8 @@ func ServerReboot(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't reboot server", err) } + + LabelAssignNamesForIDs(c, server) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -165,6 +171,8 @@ func ServerShutdown(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't shutdown server", err) } + + LabelAssignNamesForIDs(c, server) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -181,6 +189,8 @@ func ServerOverride(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't override server", err) } + + LabelAssignNamesForIDs(c, server) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/ssh_profiles_cmd.go b/cmd/ssh_profiles_cmd.go index 4497e6c..8d01bba 100644 --- a/cmd/ssh_profiles_cmd.go +++ b/cmd/ssh_profiles_cmd.go @@ -115,6 +115,8 @@ func SSHProfileUpdate(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't update sshProfile", err) } + + LabelAssignNamesForIDs(c, sshProfile) if err = formatter.PrintItem(*sshProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/template_cmd.go b/cmd/template_cmd.go index bcb497e..ab181e5 100644 --- a/cmd/template_cmd.go +++ b/cmd/template_cmd.go @@ -128,6 +128,8 @@ func TemplateUpdate(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't update template", err) } + + LabelAssignNamesForIDs(c, template) if err = formatter.PrintItem(*template); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } From 1f2b8c23db2c803ed4519e5bdf927e7a2bc0ad35 Mon Sep 17 00:00:00 2001 From: Pablo Cantera Date: Fri, 20 Jul 2018 14:06:17 +0200 Subject: [PATCH 4/9] Updated README (issue #77) - Removed 'workspaces' - Added 'labels' --- README.md | 258 +++++++++++++++------------- docs/images/commissioned-server.png | Bin 59825 -> 58983 bytes docs/images/server-bootstraping.png | Bin 33277 -> 26587 bytes docs/images/server-operational.png | Bin 32024 -> 28172 bytes 4 files changed, 139 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index f652d40..d9fdd43 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,11 @@ Once your account has been provisioned, we recommend you to follow the configura ## Manual Setup -Use IMCO's Web UI to navigate the menus to `Settings` > `User Details` and scroll down until you find the `New API Key` button. +Use IMCO's Web UI to navigate the menus to `Settings` > `User Details` and scroll down until you find the `API Key` button. API Key -Pressing `New API Key` will download a compressed file that contains the necessary files to authenticate with IMCO API and manage your infrastructure. `Keep it safe`. +Pressing `Create and download a new API key` will download a compressed file that contains the necessary files to authenticate with IMCO API and manage your infrastructure. `Keep it safe`. Extract the contents with your zip compressor of choice and continue using the setup guide for your O.S. @@ -131,15 +131,16 @@ USAGE: concerto [global options] command [command options] [arguments...] VERSION: - 0.6.1 + 0.7.0 AUTHOR: Concerto Contributors COMMANDS: blueprint, bl Manages blueprint commands for scripts, services and templates - cloud, clo Manages cloud related commands for workspaces, servers, generic images, ssh profiles, cloud providers, server plans and Saas providers + cloud, clo Manages cloud related commands for servers, generic images, ssh profiles, cloud providers, server plans and Saas providers events, ev Events allow the user to track their actions and the state of their servers + labels, lbl Provides information about labels network, net Manages network related commands for firewall profiles settings, set Provides settings for cloud accounts setup, se Configures and setups concerto cli enviroment @@ -147,12 +148,17 @@ COMMANDS: ... ``` -To test that certificates are valid, and that we can communicate with IMCO server, obtain the list of workspaces at your IMCO account using this command +To test that certificates are valid, and that we can communicate with IMCO server, obtain the list of cloud providers at your IMCO account using this command ```bash -$ concerto cloud workspaces list -ID NAME DEFAULT SSH_PROFILE_ID FIREWALL_PROFILE_ID -5aabb7521de0240abb00000e default true 5aabb7521de0240abb00000d 5aabb7521de0240abb00000c +$ concerto cloud cloud_providers list +ID NAME +5aabb7511de0240abb000001 AWS +5aabb7511de0240abb000002 Mock +5aabb7511de0240abb000003 DigitalOcean +5aabb7511de0240abb000004 Microsoft Azure ARM +5aabb7511de0240abb000005 Microsoft Azure +5aba04be425b5d0c16000000 VCloud ``` ## Environment variables @@ -184,6 +190,8 @@ If you got an error executing IMCO CLI: We include the most common use cases here. If you feel there is a missing a use case here, open an github issue . +From release 0.7.0 the resources can be organized using labels, a many-to-many relationship between labels and resources, based on User criteria and needs ('workspaces' are not available anymore) + ## Wizard The Wizard command for IMCO CLI is the command line version of our `Quick add server` in the IMCO's Web UI. @@ -281,46 +289,32 @@ FLAVOUR_REQUIREMENTS: GENERIC_IMAGE_ID: ``` -We have a new server template and a workspace with a commissioned server in IMCO. +We have a new server template with a commissioned server in IMCO. Server Commissioned -From the command line, get the new workspace, and then our commissioned server ID. - -```bash -$ concerto cloud workspaces list -ID NAME DEFAULT SSH_PROFILE_ID FIREWALL_PROFILE_ID -5aabb7521de0240abb00000e default true 5aabb7521de0240abb00000d 5aabb7521de0240abb00000c -5b0ea6377906e900fab96797 Wordpress_workspace false 5aabb7521de0240abb00000d 5b0ea6377906e900fab96795 -``` - -```bash -$ concerto cloud workspaces list_workspace_servers --workspace_id 5b0ea6377906e900fab96797 -ID NAME FQDN STATE PUBLIC_IP WORKSPACE_ID TEMPLATE_ID SERVER_PLAN_ID SSH_PROFILE_ID -5b0ea6377906e900fab96798 wpnode1 sf98aa2c61069a1b.centralus.cloudapp.azure.com inactive 104.43.245.138 5b0ea6377906e900fab96797 5b0ea6377906e900fab96792 5aac0c05348f190b3e0011c2 5aabb7521de0240abb00000d -``` - Our server's ID is `5b0ea6377906e900fab96798`. We can now use `concerto cloud servers` subcommands to manage the server. Lets bring wordpress up: ```bash $ concerto cloud servers boot --id 5b0ea6377906e900fab96798 -ID: 5b0ea6377906e900fab96798 -NAME: wpnode1 -FQDN: sf98aa2c61069a1b.centralus.cloudapp.azure.com -STATE: booting -PUBLIC_IP: 104.43.245.138 -WORKSPACE_ID: 5b0ea6377906e900fab96797 -TEMPLATE_ID: 5b0ea6377906e900fab96792 -SERVER_PLAN_ID: 5aac0c05348f190b3e0011c2 -CLOUD_ACCOUNT_ID: 5aabb7531de0240abb000024 -SSH_PROFILE_ID: 5aabb7521de0240abb00000d +ID: 5b0ea6377906e900fab96798 +NAME: wpnode1 +FQDN: sf98aa2c61069a1b.centralus.cloudapp.azure.com +STATE: booting +PUBLIC_IP: 104.43.245.138 +TEMPLATE_ID: 5b0ea6377906e900fab96792 +SERVER_PLAN_ID: 5aac0c05348f190b3e0011c2 +CLOUD_ACCOUNT_ID: 5aabb7531de0240abb000024 +SSH_PROFILE_ID: 5aabb7521de0240abb00000e +FIREWALL_PROFILE_ID: 5b50a4c75f7c880ad9c6bbfb +RESOURCE_TYPE: server +LABELS: [Wordpress] ``` Server status: `Bootstraping` Server Bootstraping - Server status: `Operational` Server Operational @@ -359,6 +353,7 @@ ID NAME 5aabb7551de0240abb000068 Red Hat Enterprise Linux 7.3 x86_64 5aabb7551de0240abb000069 CentOS 7.4 x86_64 5aabb7551de0240abb00006a Debian 9 x86_64 +5b2a331ee09b740b5ee72f24 Ubuntu 18.04 Bionic Beaver x86_64 ``` Take note of Ubuntu 16.04 ID, `5aabb7551de0240abb000065`. @@ -376,29 +371,22 @@ ID NAME DESCRIPTION Joomla curated cookbooks creates a local mysql database. We only have to tell our cookbook that we should override the `joomla.db.hostname` to `127.0.0.1`. Execute the following command to create the Joomla template. ```bash -$ concerto blueprint templates create --name joomla-tmplt --generic_image_id 5aabb7551de0240abb000065 --service_list '["joomla"]' --configuration_attributes '{"joomla":{"db":{"hostname":"127.0.0.1"}}}' -ID: 5b0ebc6e7906e900fab967a3 +$ concerto blueprint templates create --name joomla-tmplt --generic_image_id 5aabb7551de0240abb000065 --service_list '["joomla"]' --configuration_attributes '{"joomla":{"db":{"hostname":"127.0.0.1"}}}' --labels Joomla,mysite.com +ID: 5b5192b15f7c880ad9c6bc12 NAME: joomla-tmplt GENERIC IMAGE ID: 5aabb7551de0240abb000065 SERVICE LIST: [joomla] CONFIGURATION ATTRIBUTES: {"joomla":{"db":{"hostname":"127.0.0.1"}}} +RESOURCE_TYPE: template +LABELS: [mysite.com Joomla] ``` #### Instantiate a server -Now that we have our server blueprint defined, let's start one. Servers in IMCO need to know the workspace that define their runtime infrastructure environment, the server plan for the cloud provider, and the template used to build the instance. +Now that we have our server blueprint defined, let's start one. Servers in IMCO need to know the server plan for the cloud provider, and the template used to build the instance. As we did in the Wizard use case, we can find the missing data using these commands: -##### Find the workspace - -```bash -$ concerto cloud workspaces list -ID NAME DEFAULT SSH_PROFILE_ID FIREWALL_PROFILE_ID -5aabb7521de0240abb00000e default true 5aabb7521de0240abb00000d 5aabb7521de0240abb00000c -5b0ea6377906e900fab96797 Wordpress_workspace false 5aabb7521de0240abb00000d 5b0ea6377906e900fab96795 -``` - ##### Find cloud provider server plan ```bash @@ -451,11 +439,11 @@ We already know our template ID, but in case you want to make sure ```bash $ concerto blueprint templates list -ID NAME GENERIC IMAGE ID -5afd5b4c42d90d09f00000aa windows 2016 5aabb7551de0240abb000067 -5b067fe8f585000b80809a8e ubuntu 16.04 5aabb7551de0240abb000065 -5b0ea6377906e900fab96792 Wordpress_template 5aabb7551de0240abb000064 -5b0ebc6e7906e900fab967a3 joomla-tmplt 5aabb7551de0240abb000065 +ID NAME GENERIC IMAGE ID LABELS +5afd5b4c42d90d09f00000aa windows 2016 5aabb7551de0240abb000067 [] +5b067fe8f585000b80809a8e ubuntu 16.04 5aabb7551de0240abb000065 [] +5b0ea6377906e900fab96792 Wordpress_template 5aabb7551de0240abb000064 [Wordpress] +5b5192b15f7c880ad9c6bc12 joomla-tmplt 5aabb7551de0240abb000065 [mysite.com Joomla] ``` ##### Find Location ID @@ -473,7 +461,7 @@ ID NAME ##### Find Cloud Account ID -It's necessary to retrive the adequeate Cloud Account ID for `Microsoft Azure` Cloud Provider, in our case `5aabb7511de0240abb000005`: +It's necessary to retrieve the adequate Cloud Account ID for `Microsoft Azure` Cloud Provider, in our case `5aabb7511de0240abb000005`: ```bash $ concerto settings cloud_accounts list @@ -489,57 +477,87 @@ ID NAME CLOUD_PROVID 5aba066c425b5d0c64000002 VMWare-Routed-cloud_account-name 5aba04be425b5d0c16000000 VCloud ``` +##### Find SSH Profile ID + +It's necessary to retrieve the adequate SSH Profile ID. It can be created using CLI commands or IMCO UI. + +```bash +$ concerto cloud ssh_profiles list +ID NAME PUBLIC_KEY LABELS +5aabb7521de0240abb00000d default ssh-rsa AAAAB3NzaC1yc[...] [] +5aabb7521de0240abb00000e Joomla SSH ssh-rsa AAAABBfD4Klmn[...] [mysite.com Joomla] +[...] +``` + +##### Find Firewall Profile ID + +It's necessary to retrieve the adequate Firewall Profile ID. It can be created using CLI commands or IMCO UI. + +```bash +$ concerto network firewall_profiles list +ID NAME DESCRIPTION DEFAULT LABELS +5aabb7521de0240abb00000c Default firewall Firewall profile created by the platfom for your use true [] +5b519da77fb2480b0831d9d2 Joomla Firewall Firewall profile created for joomla management false [mysite.com Joomla] +[...] +``` + ##### Create our Joomla Server ```bash -$ concerto cloud servers create --name joomla-node1 --workspace_id 5aabb7521de0240abb00000e --template_id 5b0ebc6e7906e900fab967a3 --server_plan_id 5aac0c04348f190b3e001186 --cloud_account_id 5aabb7531de0240abb000024 -ID: 5b0ebe297906e900fab967a7 -NAME: joomla-node1 +$ concerto cloud servers create --name joomla-node1 --template_id 5b5192b15f7c880ad9c6bc12 --server_plan_id 5aac0c04348f190b3e001186 --cloud_account_id 5aabb7531de0240abb000024 --ssh_profile_id 5aabb7521de0240abb00000e --firewall_profile_id 5b519da77fb2480b0831d9d2 --labels Joomla,mysite.com +ID: 5b5193675f7c880ad9c6bc16 +NAME: joomla-node1 FQDN: -STATE: commissioning +STATE: commissioning PUBLIC_IP: -WORKSPACE_ID: 5aabb7521de0240abb00000e -TEMPLATE_ID: 5b0ebc6e7906e900fab967a3 -SERVER_PLAN_ID: 5aac0c04348f190b3e001186 -CLOUD_ACCOUNT_ID: 5aabb7531de0240abb000024 -SSH_PROFILE_ID: 5aabb7521de0240abb00000d +TEMPLATE_ID: 5b5192b15f7c880ad9c6bc12 +SERVER_PLAN_ID: 5aac0c04348f190b3e001186 +CLOUD_ACCOUNT_ID: 5aabb7531de0240abb000024 +SSH_PROFILE_ID: 5aabb7521de0240abb00000e +FIREWALL_PROFILE_ID: 5b519da77fb2480b0831d9d2 +RESOURCE_TYPE: server +LABELS: [mysite.com Joomla] ``` And finally boot it ```bash -$ concerto cloud servers boot --id 5b0ebe297906e900fab967a7 -ID: 5b0ebe297906e900fab967a7 -NAME: joomla-node1 +$ concerto cloud servers boot --id 5b5193675f7c880ad9c6bc16 +ID: 5b5193675f7c880ad9c6bc16 +NAME: joomla-node1 FQDN: -STATE: booting +STATE: booting PUBLIC_IP: -WORKSPACE_ID: 5aabb7521de0240abb00000e -TEMPLATE_ID: 5b0ebc6e7906e900fab967a3 -SERVER_PLAN_ID: 5aac0c04348f190b3e001186 -CLOUD_ACCOUNT_ID: 5aabb7531de0240abb000024 -SSH_PROFILE_ID: 5aabb7521de0240abb00000d +TEMPLATE_ID: 5b5192b15f7c880ad9c6bc12 +SERVER_PLAN_ID: 5aac0c04348f190b3e001186 +CLOUD_ACCOUNT_ID: 5aabb7531de0240abb000024 +SSH_PROFILE_ID: 5aabb7521de0240abb00000e +FIREWALL_PROFILE_ID: 5b519da77fb2480b0831d9d2 +RESOURCE_TYPE: server +LABELS: [mysite.com Joomla] ``` You can retrieve the current status of the server and see how it transitions along different statuses (booting, bootstrapping, operational). Then, after a brief amount of time the final status is reached: ```bash -$ concerto cloud servers show --id 5b0ebe297906e900fab967a7 -ID: 5b0ebe297906e900fab967a7 -NAME: joomla-node1 -FQDN: s22e6c216adaec08.centralus.cloudapp.azure.com -STATE: operational -PUBLIC_IP: 104.43.242.14 -WORKSPACE_ID: 5aabb7521de0240abb00000e -TEMPLATE_ID: 5b0ebc6e7906e900fab967a3 -SERVER_PLAN_ID: 5aac0c04348f190b3e001186 -CLOUD_ACCOUNT_ID: 5aabb7531de0240abb000024 -SSH_PROFILE_ID: 5aabb7521de0240abb00000d +$ concerto cloud servers show --id 5b5193675f7c880ad9c6bc16 +ID: 5b5193675f7c880ad9c6bc16 +NAME: joomla-node1 +FQDN: s6ef3f68038ec9e8.centralus.cloudapp.azure.com +STATE: operational +PUBLIC_IP: 23.99.252.146 +TEMPLATE_ID: 5b5192b15f7c880ad9c6bc12 +SERVER_PLAN_ID: 5aac0c04348f190b3e001186 +CLOUD_ACCOUNT_ID: 5aabb7531de0240abb000024 +SSH_PROFILE_ID: 5aabb7521de0240abb00000e +FIREWALL_PROFILE_ID: 5b519da77fb2480b0831d9d2 +RESOURCE_TYPE: server +LABELS: [mysite.com Joomla] ``` ## Firewall Management -IMCO CLI's `network` command lets you manage a network settings at the workspace scope. +IMCO CLI's `network` command lets you manage a network settings at the server scope. As we have did before, execute this command with no futher commands to get usage information: @@ -559,36 +577,28 @@ As you can see, you can manage firewall from IMCO CLI. ### Firewall Update Case -Workspaces in IMCO are always associated with a firewall profile. By default ports 443 and 80 are open to fit most web environments, but if you are not using those ports but some others. We would need to close HTTP and HTTPS ports and open LDAP and LDAPS instead. - -The first thing we will need is our workspace's related firewall identifier. - -```bash -$ concerto cloud workspaces list -ID NAME DEFAULT SSH_PROFILE_ID FIREWALL_PROFILE_ID -5aabb7521de0240abb00000e default true 5aabb7521de0240abb00000d 5aabb7521de0240abb00000c -5b0ea6377906e900fab96797 Wordpress_workspace false 5aabb7521de0240abb00000d 5b0ea6377906e900fab96795 -5b0ec2e594771f0b76361dd9 My New Workspace false 5aabb7521de0240abb00000d 5b0ec2c994771f0b76361dd6 -``` +Servers in IMCO are always associated with a firewall profile. By default ports 443 and 80 are open to fit most web environments, but if you are not using those ports but some others. We would need to close HTTP and HTTPS ports and open LDAP and LDAPS instead. -We have our LDAP servers running under `My New Workspace`. If you are unsure about in which workspace are your servers running, list the servers in the workspace +The first thing we will need is our servers's related firewall identifier. In this they can be found filtering by label assigned 'LDAP': ```bash -concerto cloud workspaces list_workspace_servers --workspace_id 5b0ec2e594771f0b76361dd9 -ID NAME FQDN STATE PUBLIC_IP WORKSPACE_ID TEMPLATE_ID SERVER_PLAN_ID SSH_PROFILE_ID -5b0ec38494771f0b76361ddb openldap-1 inactive 5b0ec2e594771f0b76361dd9 5b067fe8f585000b80809a8e 5aabb76fe499780a0000059a 5aabb7521de0240abb00000d -5b0ec3a294771f0b76361dde openldap-2 scd9ee0721949943.northcentralus.cloudapp.azure.com operational 23.101.166.77 5b0ec2e594771f0b76361dd9 5b067fe8f585000b80809a8e 5aac0c0e348f190b3e001433 5aabb7521de0240abb00000d +$ concerto cloud servers list --labels LDAP +ID NAME FQDN STATE PUBLIC_IP TEMPLATE_ID SERVER_PLAN_ID CLOUD_ACCOUNT_ID SSH_PROFILE_ID FIREWALL_PROFILE_ID LABELS +5b51a9dc7fb2480b0831d9eb openldap-1 inactive 5afd5b4c42d90d09f00000aa 5aac0c0e348f190b3e001432 5aabb7531de0240abb000024 5b51a9617fb2480b0831d9e9 5b51a9377fb2480b0831d9e6 [LDAP] +5b51a9ff7fb2480b0831d9ee openldap-2 sca9229d77b151d4.northcentralus.cloudapp.azure.com operational 23.100.76.238 5afd5b4c42d90d09f00000aa 5aac0c0e348f190b3e001432 5aabb7531de0240abb000024 5b51a9617fb2480b0831d9e9 5b51a9377fb2480b0831d9e6 [LDAP] ``` Now that we have the firewall profile ID, list it's contents ```bash -$ concerto network firewall_profiles show --id 5b0ec2c994771f0b76361dd6 -ID: 5b0ec2c994771f0b76361dd6 -NAME: My New Firewall Profile -DESCRIPTION: -DEFAULT: false -RULES: [{Protocol:tcp MinPort:22 MaxPort:22 CidrIp:any} {Protocol:tcp MinPort:5985 MaxPort:5985 CidrIp:any} {Protocol:tcp MinPort:3389 MaxPort:3389 CidrIp:any} {Protocol:tcp MinPort:10050 MaxPort:10050 CidrIp:any} {Protocol:tcp MinPort:443 MaxPort:443 CidrIp:any} {Protocol:tcp MinPort:80 MaxPort:80 CidrIp:any}] +$ concerto network firewall_profiles show --id 5b51a9377fb2480b0831d9e6 +ID: 5b51a9377fb2480b0831d9e6 +NAME: Firewall LDAP +DESCRIPTION: LDAP Services firewall +DEFAULT: false +RULES: [{Protocol:tcp MinPort:22 MaxPort:22 CidrIP:any} {Protocol:tcp MinPort:5985 MaxPort:5985 CidrIP:any} {Protocol:tcp MinPort:3389 MaxPort:3389 CidrIP:any} {Protocol:tcp MinPort:10050 MaxPort:10050 CidrIP:any} {Protocol:tcp MinPort:443 MaxPort:443 CidrIP:any} {Protocol:tcp MinPort:80 MaxPort:80 CidrIP:any}] +RESOURCE_TYPE: firewall_profile +LABELS: [LDAP] ``` The first four values are ports that IMCO may use to keep the desired state of the machine, and that will always be accessed using certificates. @@ -596,12 +606,14 @@ The first four values are ports that IMCO may use to keep the desired state of t When updating, we tell IMCO a new set of rules. Execute the following command to open 389 and 686 to anyone. ```bash -$ concerto network firewall_profiles update --id 5b0ec2c994771f0b76361dd6 --rules '[{"ip_protocol":"tcp", "min_port":389, "max_port":389, "source":"0.0.0.0/0"}, {"ip_protocol":"tcp", "min_port":636, "max_port":636, "source":"0.0.0.0/0"}]' -ID: 5b0ec2c994771f0b76361dd6 -NAME: My New Firewall Profile -DESCRIPTION: -DEFAULT: false -RULES: [{Protocol:tcp MinPort:22 MaxPort:22 CidrIp:any} {Protocol:tcp MinPort:5985 MaxPort:5985 CidrIp:any} {Protocol:tcp MinPort:3389 MaxPort:3389 CidrIp:any} {Protocol:tcp MinPort:10050 MaxPort:10050 CidrIp:any} {Protocol:tcp MinPort:389 MaxPort:389 CidrIp:any} {Protocol:tcp MinPort:636 MaxPort:636 CidrIp:any}] +$ concerto network firewall_profiles update --id 5b51a9377fb2480b0831d9e6 --rules '[{"ip_protocol":"tcp", "min_port":389, "max_port":389, "source":"0.0.0.0/0"}, {"ip_protocol":"tcp", "min_port":636, "max_port":636, "source":"0.0.0.0/0"}]' +ID: 5b51a9377fb2480b0831d9e6 +NAME: Firewall LDAP +DESCRIPTION: LDAP Services firewall +DEFAULT: false +RULES: [{Protocol:tcp MinPort:22 MaxPort:22 CidrIP:any} {Protocol:tcp MinPort:5985 MaxPort:5985 CidrIP:any} {Protocol:tcp MinPort:3389 MaxPort:3389 CidrIP:any} {Protocol:tcp MinPort:10050 MaxPort:10050 CidrIP:any} {Protocol:tcp MinPort:389 MaxPort:389 CidrIP:any} {Protocol:tcp MinPort:636 MaxPort:636 CidrIP:any}] +RESOURCE_TYPE: firewall_profile +LABELS: [LDAP] ``` Firewall update returns the complete set of rules. As you can see, now LDAP and LDAPS ports are open. @@ -617,45 +629,53 @@ Let's pretend there is an existing Joomla blueprint, and that we want to update This is the Joomla blueprint that we created in a previous use case. ```bash -$ concerto blueprint templates show --id 5b0ebc6e7906e900fab967a3 -ID: 5b0ebc6e7906e900fab967a3 +$ concerto blueprint templates show --id 5b5192b15f7c880ad9c6bc12 +ID: 5b5192b15f7c880ad9c6bc12 NAME: joomla-tmplt GENERIC IMAGE ID: 5aabb7551de0240abb000065 SERVICE LIST: [joomla] CONFIGURATION ATTRIBUTES: {"joomla":{"db":{"hostname":"127.0.0.1"}}} +RESOURCE_TYPE: template +LABELS: [mysite.com Joomla] ``` Beware of adding previous services or configuration attributes. Update will replace existing items with the ones provided. If we don't want to lose the `joomla.db.hostname` attribute, add it to our configuretion attributes parameter: ```bash -$ concerto blueprint templates update --id 5b0ebc6e7906e900fab967a3 --configuration_attributes '{"joomla":{"db":{"hostname":"127.0.0.1", "password":"$afeP4sSw0rd"}}}' -ID: 5b0ebc6e7906e900fab967a3 +$ concerto blueprint templates update --id 5b5192b15f7c880ad9c6bc12 --configuration_attributes '{"joomla":{"db":{"hostname":"127.0.0.1", "password":"$afeP4sSw0rd"}}}' +ID: 5b5192b15f7c880ad9c6bc12 NAME: joomla-tmplt GENERIC IMAGE ID: 5aabb7551de0240abb000065 SERVICE LIST: [joomla] CONFIGURATION ATTRIBUTES: {"joomla":{"db":{"hostname":"127.0.0.1","password":"$afeP4sSw0rd"}}} +RESOURCE_TYPE: template +LABELS: [mysite.com Joomla] ``` As you can see, non specified parameters, like name and service list, remain unchanged. Let's now change the service list, adding a two cookbooks. ```bash -$ concerto blueprint templates update --id 5b0ebc6e7906e900fab967a3 --service_list '["joomla","python@1.4.6","polipo"]' -ID: 5b0ebc6e7906e900fab967a3 +$ concerto blueprint templates update --id 5b5192b15f7c880ad9c6bc12 --service_list '["joomla","python@1.4.6","polipo"]' +ID: 5b5192b15f7c880ad9c6bc12 NAME: joomla-tmplt GENERIC IMAGE ID: 5aabb7551de0240abb000065 SERVICE LIST: [joomla python@1.4.6 polipo] CONFIGURATION ATTRIBUTES: {"joomla":{"db":{"hostname":"127.0.0.1","password":"$afeP4sSw0rd"}}} +RESOURCE_TYPE: template +LABELS: [mysite.com Joomla] ``` Of course, we can change service list and configuration attributes in one command. ```bash -$ concerto blueprint templates update --id 5b0ebc6e7906e900fab967a3 --configuration_attributes '{"joomla":{"db":{"hostname":"127.0.0.1", "password":"$afeP4sSw0rd"}}}' --service_list '["joomla","python@1.4.6","polipo"]' -ID: 5b0ebc6e7906e900fab967a3 +$ concerto blueprint templates update --id 5b5192b15f7c880ad9c6bc12 --configuration_attributes '{"joomla":{"db":{"hostname":"127.0.0.1", "password":"$afeP4sSw0rd"}}}' --service_list '["joomla","python@1.4.6","polipo"]' +ID: 5b5192b15f7c880ad9c6bc12 NAME: joomla-tmplt GENERIC IMAGE ID: 5aabb7551de0240abb000065 SERVICE LIST: [joomla python@1.4.6 polipo] CONFIGURATION ATTRIBUTES: {"joomla":{"db":{"hostname":"127.0.0.1","password":"$afeP4sSw0rd"}}} +RESOURCE_TYPE: template +LABELS: [mysite.com Joomla] ``` ## Contribute diff --git a/docs/images/commissioned-server.png b/docs/images/commissioned-server.png index e2a97303f99735a8ce66af0eb1a7e55152c49319..667ab9fa0d336c4693295855d034904fc202e66e 100644 GIT binary patch literal 58983 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 literal 59825 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 HLEwJ?l;i)4 diff --git a/docs/images/server-bootstraping.png b/docs/images/server-bootstraping.png index 2e7bbe5b8a609bdd1d94068d3f4feed82242e75c..446601680477ac21daf051688f215889fe4d8fbf 100644 GIT binary patch literal 26587 zcmb5V1y>v0_cn~Xw764R+$ru-C=Nw}ySuwP#f!VUySux)TX82y@F%^0@Bah5D=W#Y zWai91+s@wCxhCwFoH!~H0TKiR1gfNjs3HUeR5ti{2mucKnecE%0{((CkQNt(`1tqA z=`4;1|AJ^Iq3#F)fsFa@4GEE!fd~E(-bqqc41NzD6P*BJqcE%r0^$pVq^Pj6+uHf2 zs~48@`|!>3gq8diECNHrjAuM;Kme>KZo6w!{g%6{pgjM}2~864+`;9>ikcb$YR)rM z=!=kq#MNpSEAYke#}{&8J_S*M>0xMN)8dN>w3jSfLW;MF?lc6lpid$QH^{#ZKK*z2 zTQ~f`RK48X4{`T{h*}LGq=5GHQG0+MgmCiQs|`V)7BX)_W`yVVCY-ijhPYCJV}*k{ z&vpX})JsFX;)VP)_v3)CV7>p= zl?1WL%1j2NEum86GN@VQELPX$QkcK#YT56W6a*zJ-F+$Aj!$JnKM9O7=9g< zFej>s+sk8?!7Sbsk@9ki7}w#eBAu4i^(&%jkc_6FoLjxt3F;3~?_QS`?2=?QT1ZZT zThUbO?Xj_rvG8qgls?!RA9i>|&IWFs14_dC28$nTZ}HKb*LteF>b>YV%73mWfN+io z1-T+#bTh+>XsE;cYO;tmGMa}jEJ5vpczfrc`CC5{Q=O(Hij>mHmo7$<%SHcr&oH-F znhBQ?Wuv%dA}kIaQOOd!=4rPP2U=ko_MoxY2^$t`CGug{Fp+*d^xb@Km?}0FgA2sJrZK92BGKDb{IQ+Mt*g85Y+U`#l zUjKXD>CbsnhRwb<;wQ7yUd2@*=7EH;K=JgK^ohsC7@rR2AgZq!$Nl(1FeC_@-#K88ODb8{(Tji?|b9b@C z0!mTCdW*@beo_Sc#RB2G%Y`&yb!+u{Z_-uIhy&Ws;D{Jt0q&o+$wJ5RTx?q0r6cc0UBwKGql{ICI{AiO)j>K{~NWLA>tRLOfWmNqQ&L(8hx*+B6 zp?OVbo*Y zZ`G`&bDnPXt|&`V@0c*xPA4@8jvM$NFd}4edtcKToGYq;do}EgL0QkUUL2PjF;$#s zyXabipLJRJHyv-KORIn!9VA=z(9mrrY^V2KR=~@jg)O0uD-|6ahKSh=8^6~H&FbX; zWtUHai?fBu{X*uM!5G`8rYKpVKMhbD_w^#Jk#nkEeJ>-8nrf=Up@1q^dR8A(sPjJ` zMy-?!JF5#YE@~7-iZ`W{gX(7+G_$g%UK~{npo^c_*`{A(EkBD+%)FQXb)}}>kC%~T@^ItLA^4y8dn-d7q;hs}$Qk+pW}?(aNPK zRwt2EFuJZ(WUw^wetUMZ!s64-u1)At$}+kc(qbeP%$enJ#ZHJ_6o}f&)L?a8e4u<` zu{V1>boiF;LW~koR%SPN0!^qB>J)h4P9e_6_?|N*R{T(C=<#(OC}ozM^yPo<;In&u z5-DuGe&lNtXbvU3Ie^P{4Tt(l2EA|gqoORY;bI*9QT5~IwfEC@_4caS8m!?<-r26T zvt_arvk}`9^L)4Fve}$_{>vUZ$@v%K#uBe)k70D1kLUVOmJi)#mK;mCi#fv3j$HmV zR?pK(deHKP7GITayz%hE)rOV)qcisl0ql7~OJHwj-r5fxg@ezJqX!N4SIF7+vn^es z+!qxaO{!I&z)BT)4cJve&uh5)=vMWF#u3|1+AW>v$R&)hIqOYNHi}C;{hC9nouh1^ z^reyx?>T1cJyxWu-{c^(T9qShsao1HBl=p&o2N;NoSm#pLBG1FwBz~)ii1S4Z>7=t zyZvm{&s-)`U79W-u0J?Q%YRs&Y$My>#E_&2AB(P5Xv3J^?dN0OwNfkN(=xj_Jar?+ zz3TM%(_H>uevs(;nO<^bhy7@Gd|T~5US!e!pTI;)49!n$s1jEp7@060$G#d*!JQax zw-rt2jE}XAE7A$_hHCB9QxoC)gJQva9Mw6J?PEoVb3*2e#Pwf4b}(b3In+tX*fiM^ z0qeI2Ax;%Im1KWNPOms+_M%M%g?S73i#APmC)#yQ#s-A%j{i)BWWvfKnLOeEejVNH z*Qm3|#E)s9YdRpe0$b%l*SnSkfD_K;(qiyZ!6uV1Ze!Vi&am3loWc{6Es@+g>N$T@ zIhbcIuC--ef7@fp8W!P@TNZxjg8MJMG2scY(@9Ak#vo<2=^H?$mD%Y`yJcl=M2jEc zTn~jCK(&RH`2hf+{S}oBp081g^1qXlu?*{pxcz8TQo`_m)m(^sbL~sW!pdBOp#LT- z&kA%tHATgXKO8=tAg^jF+uN~VI?eAyF(o_dl|1&Rhc2~vD=)cAM)GqVca+hdGgS_e zA`suCp12PtqO@Joswgj6c=+!+y|*0e?)6H zdxg*PKB_{$IMMk4@qRY9$?EPM?M0EHlnuS+{B5+}6t!6u zIgm;Dy-+XaFbGilVRlJ?;TULHMdla?AvGFoQ#^%K5jtJ*a+AtYstXy{$|5Dy`(759G@rxrO82zZq{JZy462BlGA7q9LA$kW#yhL(lw-)>EZ!-1& zrI%p&AL5dycFNZL0NBH=jZD*N^H5GBMHSq?siPM)=15G8G~`3G4Q0#5fF}dT7PeaP zG~|8iG+){Wgx|83wZolDGKshTJmBpmB;5!cS7gLh_9R})+6D!^fAI2>xD63MCcbsQCnW2sSW3z+K=1pHxm7V}t z&ZR~wGLOR|Ts5F6&`(JKa9S^0q*0FEC@=_&Yb#*UT5OCGs+yf-nw(q$WYj1%RjsIKQSiI7xqsXCbo&{2fpn$r`)zt>h5g{!SqNm*YA5s8Z613sZ}c z+@W}Eb(17J#>aY0IqI5e{fhi#mC{kO%b->VN%ph|D4d1&v~kz~yJyyk|L)tY!iJVo zglJnyzbkL337m`DJA72D|3CHE_aE(+DhSuEq@iR~5mK5uMHtGMpaf=ZN|f>jjjvf% zdiN7JV|o;c|M_frl<=TnMe~3$Cu>8Hgw*p_2hppei|C9D(#bHFuw5+Ix+C*#(Vy}K z=hDjQHd@g9H714|`=1d&fS~C)Y*Kc#7?0m6pe3&SctSwu8D+*if7S(s&KSQe?^o_h zTGvRKZ>+Pu9T^004PiNBn0ve2FD`Lj*7(yeD={|;2DgHa@vWrJs=15LVr84Z)ui&kUbe*jwywYgL|VW=>hQk0uL zAVku!Qn`#oHAOqV zAA0?Y7RWA0S!8Yp63Aw&;BeBmb&*l}p9_5T;^1Ua?m`z+VBzK($}j6{^WrQv8QQ5S zfw{l-bcyII$8|9>Yp1q&ND_ z!(U`cOZ2oPjB+e_z6o&lyW{FH{OKrN?#q-|Tkm5gWLryU^X8ld^WSKI~ ztUVZa%5u6-vkkb)u8o(8KhOM7Y?I=}SIFw@6A0fuYU@+uJ}>I3I`+R%0$l{j2k*RF zMdnnGYI4XPWN_ATcUsaDUS9MQf16iR-5DaDQSWL_ve@oz$|-|-u?(2lR6zAxudY9O z_c2O#yyD6B97uE7P z3#t(^&~a;o^4pZ$v@$MZxm32wXHhlwGA}avGLaDkK@~@tVmyc{qCY`YdUj{Om1Sh6 zE>iuV3dGlsK_eO{W)C+R@W+?5udU_3luxX_f!zQ@(I70v& zjQx`SH!zoesW_6?C4dx@cKS#{9!n7-S@s}a_F!T|MN+ObhKBUKV6L1MJPp<+yhHP!t{sA=?HXM#^32^hyk7zfvxgJ%ez70H2Eb|_(EsO8 z8T7lDLJABE3~4{V_yU=fkjx2`eIIH4H_L^=*%0HuX{YubK>}DwhU-CW><>5lQ0Yq5d7;r ziLm1TqSST@{m#YKUq5F>o2xxrYM=+GU@a%Z4-&lei`P4`IZg5r(wim_ibea$@Mr7t zKM}b5k06U6%YuIkAMKxWzztf6*RGsyTY{!@(~*}8(kV@5LNBX^I`_VLaZ4-Es}!WY zfr91vcZxTNznb}pDTUds{;n~nI>%>`#XR72$IMq!)DU+PVMDtg_U>9cwXl$(sx+{6 zU;(RyOBj5sp9u75^!6vaf|YeOl#M?c#W}Jnn-cWw8T|toj@c=W0l~XHG%=HWRlD^b z!!9--Z7Hm9&EHnd6#+W^4~l}znY?~~FmfQ2|?Z6x>Cj4T4Xl@LzxehkKSIS z`vw={oeOLD*(?W*gIF%@33a|Ge) zqDAAEN!{Pr<09SmsKWAjEvtR^q06TEj)ug^dR}Jk^>MoB6akXmWHPAN^ih`Y#kcJo zwXWya5~#(!Iv9`20;m4{vy~4F8YD9utO$NBRNrIL2wAA3>d2~eH-kMNW;X(CF}|BM zttt-}w3z8_Uo($`x9K{XA4eB4I1a0rjv%80jjR;aIV0j$VDjyQ4gYK3!qZ*(f!5;$ z=f>-h)Y3AELvfugp{=b;z3U{Ljh6)_ZdVtvpkH%B>HMMNz0$Vb{CI?$wf*O!qNLn~)O%uBa)qRxY9K$AWNF8?)4JDPc220!q}l*j4IkIPIH6Q66E<<5Fc>fLHz z&zOxdf-R5zZ;TyB0GpI&c0yV-8}h$Z2M<&xilTn5#j;0nf?X4dAE3zA_BYw4Zu{A< zHtyF9jGZ6WS*eQ~DNTv5M^Q1YAbDH6^>O5qf$$;{7gbXb=x-oX%U;)V>-$M6J(EDg zTceyzXcoQOn+hc*-Ef&_4%l&T=#aAag7VY>JkQck4LvcleSH4jt6I0GE>oRWM7G=9 zS)aSC7}J|V^}LzKw^gUa>v2BwxMB zT(sQ|X%_?co=!S$*7$Ba?>%=a_s&`g-6;h_k}op{;FZ4DdXBeT$2K~pGe?>~W=%;a zKa#KR4{pyLh1(1gUTyT34!MMA-{WYWrDZeKCdOA*-HR)a1Hq;#|Smj1}Bw&eC6klIy+{OHv^nStM(+QR_G6lsC?-hxYAy zN0E!D+&$&Z);FK+<Co5oZUr-F$=J#x3x9D+sk$hAEo6dcwz`;dKocO8gsQg8ay4|KWH+ z^7b3ukT{oJ9tgRKtsPByB{we~e%({lN3$y$D>nf+2B^!z$8R35M8tE9W<{1L(DueiYIn7NTTgakD!Tauw)(yEW(QYI(T#N-Z*zt-p&?Y$!HB&wOCS?p;F5Vh5R%lNJg_>Jg*a(FT z$WT%p(@&N%O>Wl5R`ZW)v5ic13!o=&!HG~*Y~xNE=V#;u>QhIyI(+|Vv4ePPYuHcI ztUAmhfwb{9!H4I_Jq8p+cBlF3@cC_Jt~)G7DS-^zH3Y`1P^N1v$vC}L2HU5%$M#iY z4%VcnjfRjpW7}MCTqjE*^wz^+Cs0jTt_Eo~oR;j~?ZyEU-CzAL0PwE9)5TZUD$bO^ z?Ct=|X_BeV$Jc06IY8=d9Us2TfBL+kGqx9VoNdhqT~Cpb<6Q1-44VSYs|Ya>+@O`$ zh`7SbRTDe^`NeQ>U8kUH|<03KC4tO8DZeG`J+$hwnXPd~-RDimMbCuiw4DdACn+0s%F1DISI4Sgw4|HrcP!gu1=`8W*V@E)H!QXCssFs!x%ApPgOSJ z@5X5V(|MK>HRx6o=K|dzMc9fXSVQ+yO!s@tu(Z?_WTXsgvPfD{@bCV#+ab!K{OTzR zQV3EYXXr7pExc2B2M3YG@y_$B@NfB@(Rn4pq-G7PP*kAN!5HE64*FZ($W}+Qy^Hh4 zJCdc`+3(T)A@&X{R>v^cq-Jv#8exK71rYP1rRc@%m!Hrk{VQj&&B$1!z8#}-`j@zP zhDC9AG%LfzK0uH1iker3O{>a2OhGjr{HXAxOtB7%*F;M~;xF5u#k0}G zw@?dHJqs7^$SDh{SjxAewof2zZLRwUlpmY%)4-vXJv}M#(sB|BCUHx zP64Qlg?HfTa1FxU?bNA!7_UA|p6i?(EC`Y)zMJ7;xqAs2^%rle{|em15lbMx4lGR} zDz(&3U%<0aRo+7tk@+EUU*AbCqXSQ0bF71BZ6I(k^CQQN8it4Jx zA=1?hE*9hv6mMzLqT+NJ8S}NBV9Mr)AbcU24&FeGMsmmgbxx?$)3*+@0}CP58>^U_ zwV*Rp!J8(UBM{O>*QkeIhkY(N{?(N-M_H%Z-S*qnke7zL#$Ll+`F#y?<2X_-sg9 zU+B2KWo$95a?XANe}GC(A|o_=`?t1UcmLeXG@=EzH1>O4W^}~@pNlYzPtPZ-ztpXup!`6Qo>6MB0IX?JmZl5SRcZ$RUMfg-T!9ks$;!5;bI-@bitqDdYKLi?8Sr- z)3;mZ;N!67Z{Fsu(07c4wE4HYe+ip(vU$)H={|VF1d9-K48@H<3 zj3n-g86ce5FYY$yoH^r9a-^ZTxjCIyS%}j4MEO3${aU(yM>Nve z-(&||M4xA(gDz}}3rOLJ3}4@3{_bDD%-!Lu&CuQb4w+?kbUAQ|HdF0_Z2Yu)=|L1M4;@?76h$I3U9J%4~pegGgW(jk__TP<+#m!G#W$%ZgA zVE%WQLmZazkHLb5g&#(WFp~>D`;RT9J;G^c*WMhZB_;VAn4;pnyAAOqM@Y^7cb?SO z>t@*A*IOeCKN6HZN^U}DP+{Bqf0h>@L@z?81YBykB)Ln-N`9TnT?&NrjfB1eHb8(lVp1;sj;jn=`nQDSS4Aa)Xk%k{>_TTB z5lSv1G(`-<-!*JDEx%N?CA*e|mz9gPWWbI^?iId|B`_GHL@MC>c+Q`(OraPZ`mezG zCYdh(uRB2?+g19lOED}B6(8(?@z(E1gXZCupecc@+h7U=lXBY_jn=pBIniS zX`#11wE;yCO34q6!I^CfSyjmaC1Hxcr_E37FL2Ee}(Q z#Y+()Lw}*2Afzma@Vrk*qboW86HWJ*cFzFr_fbY<#q*^DKO#)ZA^%!_&DR%&ib1E( znAY+A|5}^AqgtG~drIqrfZQ1tYe7>yxM##UlB?v~^EwNX6y4E=`^MMz^6xkYPtjby z&Rai|7&M2%+0#p|-=qS|Jkd2>6W`?{a4seaPkWGU$s)S9e>cAytJ`p7ij;m@(B{?X5a&p%%5{#IsAvgqN&8s>75C#+$HOcwO#dbJ~{1@9;7#q`Q9 z^vQhZxB?UB)7|XdRbp^esP86p@0Vp=o+t6j{m_6Tmid17|KkGqZKr^GHsw^wwGJ`DILoK_h3n%8aYEWve0URg9?Xv>CeYmWu95U5a)cHf6Bi~K_ zgx7uF@V|b#OTIctKWGJc^*%pImsZ&4r&Ati9@e;7QV3(-wk`c-r?8^}s#_;}D-t5Huuyq% z;g`0n#0Hr%eOkcHg{}()z z&nO`?Z$41|dR#Ry0ie@lL@*iR;IugPzEGS@8(qF!cZwM$Aoo{4pTFMkKZ2ihd?m^f zAvc8M1-&(UaaWz4r0Z_}n%`*s!R|ebgBWy%)0Bde#l8Le>+iYj`d_uZJgbJ21jwS8 zr|k)SQ+xdJuIEVWMn8ptbEczTY6@d<+`9XSAuI9q*y)wgx2FzC6Hn0_brvqR8)D8m z8Df^Fb}N>MH0<` zk|tOFMh&ukXfTeB2ri@n)28{d13pM1H%^juB=>K#U0y)jFCy=DHEJ4$;=DW@ zVlhSta02OW$*yc}hgxXmR|YIIdk+y^cDf^}L%x z;-J&@ZvQRxWu!#^4NuSlfkyJ?_2ML10V^1JnkQms#3S zcw;)q6xE=a5KZ`KPJDf_qZw zWpengUyQ&EZ+scX)Js_Qq-P*lCiBj@Vuc(IE1eL5brmxYXF5W42GzSTdNIA=5Bc=uW702~y zkP2^#C3A7ETZfTzIoAyTYAVmLAzl|&$A=A0#9Fw|^Ck=q-xC>={YBx61V)l7gQkD1 zo^#NaS^fe9U$j5exxdJHIG+v7ymyW;(GOkR-^L`r2QFIV0@gk`I6U4gp2KF?uSs~g zOv20?T|SPhce!9T>$)O-W~B12Yr6&^+hQQWxLcn7px0=P=0;9taeRRK8G|Pp8mR(N zu~w64+pRlwa3uS8+!nXaZIEY*&l;=jYj+!jJz8RJC9zlc@c2XBmv-8K^XmE12Lfd` zCzC6udBM$}{e}axtF^oXIbr)F%DFDvv@WX42AkRvV-oOsHLQXXFTDimkIe@G&mw(w z7@Wj)B35*$u|@!)2-CR}=#_RGmhA*IBN+qs(Tl#L3MWm*(M?7ey_fLT72fEAsLnsP z1&jkQ;j4tHgA&Moq}AIYi}lF;iP4C7Vh1p+-xVH`cTVS`^nB2&wxDaE6Kn=CRv`^A zN-{q9x08%#e0%&ThX?9;(??orcdu+bT!+Zv)EcMcA9Wa|I#UOduZFF;*QCV%KR+EN~_;h06i$lE9nqL{))Psxn=~klqs}jVvsTk*H|)} z6}q7Tm!(B59ViiEg&iG{e{5KE&2KM_+v;#qD^OyPG_SYAm1 z6kw5>p&9})dzacgjehN{5Redj@Z{5!oTzlkZl{i2bxbTO(r4J1z`!=}zp+Wt|_OaJIOXr=b^`B13 z_6rA7c?BGao2}ef=M1|IpEp<91wtA5wf|aImJdEmpM_Vyp0JltZz+*s)@gmhWwXj_>`Nbhk!lfNt6PFwSuZ zII_>=aCu?~x|(ozcz^`&>&&EZAz3)Tw-%%YqW>rr{=$FXBM$(Sc- zD{~r|5K$ttm=RSx-O#g?r_uyjI-la_U%PdxX*B0Ldf&gb0Ocr*?03ka=lA=wX5rGs zez3eS=PZTJE-kmJP{Z z|Gnau-DJ(1XZ~s16)zmlr2F~sD}IeHN{khCM5g7G1-6`2Md(x(oJF43FmUut=We5HFmr!S- zImpxDJl=LL!}IdqQFfo-9=AW9vK$J^sNvUwNDQIj(a7oOq#4=%*Xvk+A=r+39eb>{ zzec&LeK3SWQBrQmZf$aZ1>Kx)b++px)adfgv)Q+58JU=j!G8JIy%c|)clN~RXzJ9@6tFS5ey;?YcU}o8PAuG>!clGM*hs^u-)Ko$P#b!M24h(@o z$x+qOg;LVn(=n0Xkw=H=OuI{DrNd5`?MGe10SSm&^GZV?tZo5EUEgTBZfdb!7%^dV zjh4sK_SRzY1(vmdWF|pf@nxL8VvLpXzf|Q zK}AtU<3o`D=Y6pAqMRr~!t=^lLYy*KGANi>V!}$;Wk^6b&&*)S%{&%`o)X`_EC)Mo zunt~L@cCZP1#DCr!WeWH_CpLld= zHJmmV*3LAtH~&#(xB>en*{Am~ygg^!r?w{6UvW}y=6lK9rhY&lmc86^_93);U-Jq? zU*UB6j$6b9Iik}TWKJh_JXbBaj56M%t21%O&lA?pSyLuryf;90uG^T_cpw}iPc&|R z!hxj9Xp|T`G4 z|C@ee(oxr{2X`@qm+2}d0X1x2R)IY(o?di8Jm0%qV1Jt(+U zF#j<)--g;7=e3sYf~M;Fof}y7h%Zoqj?V&jwtT*O=*d`Ucdznbt>Auk#=}TTFsYlR zE~T;T)7bEYMb(RX!QryTW8a^{W`(r!b+6^D4ASdnN$ab@D<5bq z?ONNqB!HaC32|@?g+7m|XH2 ztNXY!MRH^Y62S%(^m3TWXMd)iGHwb~&&6HT9>k3C`h6eC3R)iw+pz2Jo(YLM?2Tx@ zo$1W}1*2?KeRQ+>wBdYzA``he&0T$N{py&9=X5TZ;`^%4=P3A^W}w%XBPVKtwl`}T z8fyds0mr3VL1m6DWR-pj=wJ%@*Jxf}YsBKrk58LZo_}ttt#V{zV$ao5G12nslQ4-~ zDWjisZ>`5lU!US;H{5UV1X2=FT~HZzjBXG5TTdrGYk7tTf{~cJlWei_3LP!SpSo7L zA}+9)t(SeZulHrMRL<84;QWJjkSHofs?vx@Dl8?2ZPMbd`(g%B{uPx1Ry#cpziCU)1B~LNB{ggx%&$mGQ`6Vdkw+Ox=BT5XYUku9UI+B&m z8Ng^ip>E6+vje;Y`@y3Aqx)tCA4 z8Yvdc-cj3tQ*M#-Kswlh@TG`wAHMl;K@>F)sT+Z-_!0|Ri(d$e& zTX|H6tfl+;WH46i+d1@%tjs*Z_VI;oUs9fi4(M^1ue%Cw`_}mH2;%4AREFjKnkxGC-mgV5x+nF6g)`@&~$U` z0f+vCwa*_^S$#@JRJX4YS2v-_xC+b6{iBj)n}fEx24C45UkK#sN^`lmKNGsxw6gi5 zbK=InZC6INJZjzD-lJqOhlEZ~!R&;!L*6PRnw{SlCTPUyM|$6|-@1f?*k;nt_HvQ> ztWUgzhji_DT+xs|l!rbCWs)&|$TcS=%eB~~j8 zm+c`uo3`dK#@KQuEZWNTw^wuV$qxxVRB7}!@%TY#x&qjrcX9UIV5ZaR4BqcR>ss$S zY>Xa0+z_MtKQRPtdH6$`V87VhrEXxgMxT*-gwmjQPmf#D3bT~n!w~wA-RVJe>tn=$Pq^rt4ce4sBhmVX=BO zBg;lZk_XUpSfz#0QN%A#kL}-Dk`$=9I`k&cxffWo8HQ3RWF|rOjD!)FdO4w=*t>A^ z@8K4zTdSQ(yL_K&^A~*DGVXz`pUGRl9TTi3mt6d9Mg62~$7O{|ksOV{ivugu|AlUB zI7OPYXSs}1mwywtr01Xx^GjT5%-!y}^riU4M)o-6ohY=~c6RYvmuPx~E6ZbkI_UA9 zzvZ>5yMEkS{~R{?)?rwUn|qHdeD6s<+s4F$fON|3_0O_gRfjQ>DhbDOM>3NQ$n#1v zd8YmSs#*~E5^};Mu%e9y^!HV_GFKPhy#BHS+_!7}#eCz@u|jm)|0IpHu=KfXOl+Qw z8?PJ0UmK}F{g>Cb=g@rmR&xE!T~9`t6*#{5HHfP&zf8YYgP|ouj^r6g>;~lA8{Hnz zz3jrDXFEU$kKbpMMf3l2<&|${ftQPOm|QM~a0C6(_4;eHc+p_DdtS%r?~H#-2K+j; zvI@X)XmGjS;K{=3bJd}0m0$N#6vOw8uZ(yo21~8N3cdRMhjyQ?pL<9VK0FVc^qSXi zRG0Ol!oi>*_}QMThn!VwGcex;4<-gz22+a>j37~ds&F)niR`($_uuxtK3uneEr$gF zYSrjrevrvDQ+0IoM^Endu*n8AI5Ec57yf#m*ib#47VJ?sz}^%D zVlUb1%=E5$xRaA?y z+B0?d+-s)Imrn*K9n)YX1&NSN2?Cu^Tm?KGRc6Q4-jJ(dX*1bMQgb)#~a}FG@x6k1AEcD%j zO3ZV~&;v*_qCL)AEr0M#$7nD?^88v9Q}-k=;`a+jwLHl1xS3xQJOIzdzI?h@+jfO`p%E?WYFp zAr_WeTlWq=Pk)WZxuRx5v~HyL@~qbCAziKq+qi$`%KUnxs2oh zcW&@Pghhjp#T1Sc7qPPids37_ZhH)s+;_r`Ks$@3t#@^Vy(arZXVs#dPH`Cf+=d&} z9#MSA)x+aYrrA@A$yw9Ix|4_Xsg9uAP1O{qRaCqXN$P_6MkV+n-y3#ZU1!!C8c8+n zseR8T86_!xa4`;XeR;k}uhsqoE$AQluYL1pHHkK%PJ0I0`~3j4SF;;8qCLDP1sqhb zNqL^Ej7_&aQvOs|r1*yeZg=}Z3`7Ar#Y~|z)moEW{uD8zY(tCrjt(gSC^ zAlOjbvaJc-e9!U_%7DSMdwtOBhVS&Gmdn1j*V7gqif#i|X#Zz4b2%zAf+BphcF1e}~rQ zWcC-}Z`-|j#`72w+kMEHWa-_l!9?M(`McGsNktin-wR(YAbmx$IdVP|{o1#8Sf5@N zvK~pIR%o7Ax_coe+TwN+^_cQL1l(|Y{DAzWrZskT`9xS8O)#W{zBODRT{UbX7!8mO z5jH8F2=NB*aNFN{lu4UnJ@J3)F1#)^r>)1Or{v*BrpNh(eeKq|`?&o5B+s&+HP}hH z7&D^K9#CVR7D>eM=Z>TwQ3b&2HGyE0=78A#cv$ZC^Fisv29=T)5QM(={t2 zw<|^}@p`SRZm&H9Bs(gmQmg_TxZ^XzML78-*wX!pDh|gs)oF9*9MSM;c>ggzb(KUp zk@u=I(NRti+ti4>7V&1|W4XQMX>B~M;RnhyG|I^~VfkPH17i{e-SSMaDUMt*^ou@4 zsN9i${%blV%dEo0$E!BZ^y?oWr964x!N7sj^^j-wOz96^9PfJpWRs5J9iLA7){KRP zMg54V+@%Zsm7XpbsNQEZX-YkV=JgsidaUFGMP;qAjE}CtN7k+VXF8_6$;bDIMT@}` zJHN8q>_>s0O3HhNhR3s&tlQqtq=(+`XqwGeM$4BS`?~jx|5{*sC`+Y?72DG2R@-!VO@K6u4KUT9>aJE zU@>U*nboVKr(W8!sdeU2gDlT-x{kQf;PH6U7DI|kVW1#Owv$dB^n*mAlF;3}S2EC` zNSGcFaQ{7n0i(c<9Z~cd=>%`mO+xbSkc6KASK%7-+BZr4=W&l6|06_ZxMjmWL%;rd+ylMXL-e8j1VnFer zL2rLQw3tGH(0`8h22S5)b8J}>YOUNWRve|$|76qB-BnV+3V3@X`e;(C&?>7)h-*ib zxc-lP>x?M?R>BewCSBKiP}Yq$ks%ZSVBD8KztU>S1c#eUs$Dy2Gdw+gG7V!9>kKBKbX(b1lf+(5O5O}OE+7pjK>@G|(?XD}2)5Uo<4 zn^Tj5y6&>Oe-V~ljcz)1px3z8aPLIqsJ46cUB<1p@a@y5@dIhi$T zymR}0_UMdc`=6d0Uym!3zJAlF-+!TKh>Faq=m@x#PE61(E2t|=6Xbt}sNZt?JSpFh z0SgcJr^y_@cq+s6;_$)@3$>Ow&;2S27vys$2c}eT#ns`YPllA2BWF+hl&dqpzBtOy z2-Z?TzWYSV5DXmYG8Jn(k)v;LMla=(-JNC5e2SYiDcbO)f}?_#R`r!H|D#7sOZVh( zV!^k$iHM43aB_O3-e$qilc{2%_0Pb2stX3JIb~%74G~T-9pk~xNQHk5@aIjZimIj- z_Rg*dc>`|H`KI0j#hLN${$`Hv?rfoM-0$rF#l?#E<_K*C;<7wt)mE(61!j8Af!6oY zfwz|R#|zv^b#+J(SUFWy(a#aaR;#H}w=@0>7c3m2>r!w~o3rqH;b>Tw3y0qnG zG<5WEnGaN&7XKk_1Jzgp*kbxGmu#2r$A}pjD5_l& zzUS~ShbeF}Th@jE?fxGZK;Rv8=VY+<^R0rjcnzTAeK_6mx;)ICoh}{&na|9{D_!{? zG^i!Ns{{aqpd9z_$$!l5MaY?C4i2wJq-;roB~#_AR=|pMrhN78C@rZFDYibo6mS#> zyH^a4=pbLSHy=bcefI>nVoeI@2YEhS5Vpzb1@Pj zFYr@z8Pd(uQ;+%h>GkYDIb)psa7o{(n_{X~AW#QgCks!DK!X8@9MRh8`<0YQO-5^3 z&gqP3I2DMi(UBriy&PDMOici;KmYmCvLQ$rkl2#qvcc}b1um!4$fNS?zMs)o_KSeP zgQv*cGgN`DZ^o)WZali#%^qj5+E zLGnHh1nbZ4JpkAEAi1QhN`JG6nEL-!_TBMRhJXJmBSlikRtY6p*|U<7%s5t7$ljZT zk`W=PY%(JAnAuJ?+1tt9Wbe)Mx%>WJuixwW>v^94>OSY(=f1D&^Lel9x(|7F3htPD z73(Xs#+3(bUr>FHGy)eYb^ViW8_|l%zECElmQ2jaiT@Fx=CKjV-?``{vNqU5sYNM; zM8D^6+mHl3!2kQmPu`dl$tC7{7QPsH&4k?>P#LA zdtQnYi&M=6HdVKL5jF;-#=NAKoH%6Kod1)~hy%CV8o$eQ?9S4;+fPrqDA#&ue@8c- zUarc7NIE3Jxb`O*5x%HPD7AvE63)vRa*uL<{u>8)C%_;Kc8+! z^;oKKwvVb{--~3_KQriJka|vXVODH_+KUgALO3KY{OLppLKrtuJj0zFOHb!DD8S}o zmrZ%7)w0K0@7iEU9=xz4{XHBoJ9);^l zG;VHhtVXU^r^&f)J;_p|*{sqIOdpklPd|UQqn~x3%XaIrZk%AuYVnfMpQkqZ)-TSU zL6`vUo&QF;&iApV!>;|GOx&KA=~t|`0x`bBM>l_Spf;BOuEH*Z=G_kyeNhZox`aNn zFdWi#oa~co>%>NG?hlyqQnRVu4Tb$C>5JDk%L%8No9})rJi6lLwL&&Wj}WiMHUl*)w45#OcD!8N1APnGX>I82N?|DsTG3)E+6h^FL+E@RmRgLl?=#dF z1-hZ0JE?T{#rEF)jiz3~+^3RMwsmk~;`|jVF{Cd1wRmVbwyoVtV6c$5K$W*LFDvQx z?ZCJ7-JM0cdN$0z#v-QD7YC#B{f8PwoBHEkJ)!4Qx`b@L=QTQ-3w;1eSPV-dFt0Q78$GA(nU~kg_EqK=E8-^rqN=Rk~fl+=! zrXPkjQ4E{-$~r17^(wEr%yZg^r04AIv0>4(gSwU2n%eLc{9hU-lS8~?SLf%Rc8`08 z3{1~n%V#;h?zGLUmT~A8NN5-OJ*mEBpN4DQf2Wx`cQF%t94v#s*@Xp1b7fFSyJ*38|8keSe`Px$SRe z9W91Wtio;1L#>LEMA6J%vvJjyRF1|Uo<;wAHuN&4dd*B7b+EhA?qK8jz$u%QgkK#8 zwqUf<`Pdkad?ow2+B_cieyZf7p3Io;`cjKX&Ftsbk$)GXPI^^SI}QR48*h;gRo!#A z#&&}R6Ofjwkho_+MNMopN5NfUqj({ZeFMv%*=cHo`vxiTy6u&WTYP}j2g7*GAv6zV zzKN&0-w&I3LB!xe`k)PII2EMe@)- z>p3*KvL$Ol5%MLfVU{=HChY~L^}CM2i@$9Ee24F4WUx`1|F0$Tz9VOj-_jc-7 z_Q?8%<6P5wHfUEjmjj;IxLDJnR9@=fnh;T{k}?5@n~SusUpdwG)W+=xsdHIAxS9Cy zq~c`D@OI!MTW^LysTZe-h62KZc1yjQ#ua5ltKG%B;q;#^kzS3~ z>Qr&Pp`MkKc^daB?z@~}$-dW@eg`$9_$n=5)mBXFC3VZ zYjCVa|7x86=yl~C^p8m_|5#(~2(eO^DUZlW`K$HqotrFZoNTqS5gVf~U%u=e9LPQ# z#${z?UEACYe~XY(^5<_Mm3^6c1_kwR>kTB8$ztP(0Hy)^j0^ApEu8F9+f_pg!mI6y z%CH6xQFg;$)2l{5^chPecY*VebCSOY!1^OYHZGkWh3Z-;q};*;4+)?KdhOSHbqg`S zDAl3ZqvSv-oc>+Stf6XIB;wc&fcz|UXgl- zh2@-re9fown9Qjixd@EfzXs7hO~GjWw{GKSD1EH0J&<2e@W!hxfc*012lDd74<9}} z?QsXK?+k`VLG0|n@9$3|eq?3g{u&vf^YS`9b&KQWbKg#XE$Q*@iLbW?@4ySvcL%nS}?uvEG*fU|;3L1;-19sxme&w{Os3kweq zS$K0@LokimSe58%3qdGm~dGC8dg*X%}9tv*auA zJa+%PxJboqbUrF7>htH%N}8HaGEG+!25GNEC z7Z==SY`8JqTyc7G_|D&7<@xic5$rni!=(kPy&Ys^WbcZL`ET5~;jz)oGHl;+k5KpcHmyV`pb)(<%F0RwmRC%Ft0f=J`}xo0goM{PE+* zumwfl*qFJrv{WNo?da%;lE?INdU`sofUURPR6|%;Sat4;+NQR)mxnvcgUil-#57X2 zwww;Lt%-2oWY@0sWU6yaPEMvI0gOUboI-;rli`IoKeo5$e);mHEp8{o}{&D_5?ZCn9>B*_YI_(A3m4{)T}5fil_2>gh@7T>PsL7)YrPlxKdy zgfSuJ8?<0fN=C*M{df0DdUbX6Mu)&8B~{hBw|EzoP~=`-US*C8Z~M$=!WG4C2nu4k z{K&@FCTe5FUQ-84O`R8@r>75ba&vPN{ApN#RU*SXCMm|dAJg`X>J+bhB&1sZmvylU zoDHTlZ)myDdL;Ux4?e8(Du^8}-G@S)JAV{x$u~s4>)W^BNJ)f`1F99a%|Af(8S5;= zPl)lwD3jHbddAR82e*|xfdhNLdj&c*FvuhV02$mSLna5dlbFrLVe$Hyx`6*RTB-Vzg|&+Pj%=`PQF zHBB*r`NoYm68@LC?5AZEVg=rGnOah~>gedeNi>tgHs#YV<01t|MYkBCJi;GL6K>PYs zV{)IlW`!%)!oq@F6t{1O*x`ryc~htgC9--urk|mqp~-#sI5{s@>W1o~=jz^Hg&yLB zOA_StN(~4&Pr*}`Wzie6C*)bk2vBSyI zEb!afa$Kp{Y&TBq8XBU3GnSVN1F(4PU`^zrc?i7$bT(!p^d@I#rS$c$JD10$q}=+- zrujBJ{OVN}g*t;gfe=PhN<%jB4bW_VpAp^2vnQMd#vnTp09q@a*Aq(LfR7)MqxGkWyY#C?5z z829tMltij1)Dz!f+!S^YRUp^JWl^Zcw%&h6J_m2%->=d9|CH%nM}mA>5BCqnvxcAW zuk`#RrU?3yW4BBt>~Z~^y9>efoY!Qdy2K+@&gH*hyQ!Dsfe50?K!z7cW@K=i%XrCh zpmN>jbS?RWbqt2MuICYC@-2NU>kq(MChD!O;1cYeUKX-(k?Zkf5Nzh>ap~Wn<1k6c&rjgt0>YugQST>sof3u_bQuKrUURo4;>#n@wK!((= z04XHy>fQfWNKXp?oK{wAp-^(nS4T%jpXL5jYsS;eo5(*dPezA{S=*tEP#Ui0ig#$( zDV51e7;C;nRh|pj`YfR^ETW34Tv_Q(%pi`_3{2uVJRD&2u|O$c>YQ^Vqee^{KVQc1 znC+8cN=$aYbe&{cswHG5R*m|lk2Y-F=Y4!nq$BgN#cRi%;-1!HYwMh;F`#6mKRni| z33`uhtN(N;D=Sx@93P%0?<#3&v6Am!ydNdk8p9u|SzrtV+YmD;TDkMbLVW*dXL(4_ zalVsxh#|A@H^b?kbk*ME#S0fML~T$@sAxf zM=dDCi!|Tw5ZTnjq(w(ZCmg#a2-w|}N?!M3t=-?8D*%OH9Gb0>S3@d`{)su+MjO3+ zc|3sbXl^!Jw<{k}K%x3LsxUEpRyCGtgu=A@z7IiMVC|g>J`@;1GT}vv4)p410yBvU+E9?jxEYa6ciNHMf!Pi z*|Z7+3>!mq>^^8$twvxlsf|)k8&!LDv5r_q=@3MtoFfr=pr%HVEEPN%BE0e-vrkvq zIs#LScacH~s97rc20MEWc74q8l)4zZr)@}eq!DGoL)|#H-T2dM-#J?Br|Y^wquaOd zb20sunVE~^*?#xvID(x<920295_HkhX%?drJ&6(oWRj&x*3tbEPZujHJlWAbEujQB zCMG70KiS7HEyb_*51z`(x}F~IqFsYCGPKJa-xJgDY1>tt4(5$@m*y|n2OrW>QN344 zJM7{%9rphkqPITuz0V!D;%2?AVQy5L$mxK1Qr8i;4mX%|6O%bTM+Syrwbp&sOhf$4 zt6wO7lJ|zG7~)BuA-6Zrma|-44_{pPQ*_tY*B40a3MuJBb92_Nt}aA#efo6e&rho% z%l%g?r34%t9BNq_)&1GeK{k6FZx8J4@4w)V8q9rh4mcITeSUs^KI>r_Yistryu4et zZmrEHhr$XDLa&sZ`Zz$K5@|gX3k%I}&Jk!7nUkj}#NFiJ2nF^r?ueIv`0xynWh%^3 z$Z=D9yD{jTrIGT8d(VG%7Tf5>yUkLy8sEQf+=?WyO-(Y!?Xk`ELDw`3O-bQuG4C&8 z;7X;+otMuO6Eg`2P+lb*AbUVNBqmFhTyG*9)H5C%t}Cd6Se;DLqiGlvH34SaY0l>$!ksq&zseCbcC`b zcwC8TYgob-&@k6(6a|8^LVE23)qRi$mVXN0BV$)=198Kqpxef+J9qBD;z|gJUoMvw z?Pn?~f?g*ghr6pLTk~C(!zE!b#kOnX54*d&Wn^Tob8>#{>{MdCdv~Gxdr&fMd#qsX zY+KAiXX3eV7DeXk*S%qlmD6!v38CoWJ(Hxh%3b?lb&)cjSRYN3FWvYlv8o*{!G$F)%RL`S$fSwU40SL_|f?v$Bj%j`odAOv0n0 zo)lY;AV4_R9yisQC_yXg{uzX|P>mrNuDfbk*BKdoK+SiRyVx+xM?(zD0tE*hZ98OD zY&Y9VHQSM(=;Qt?!DL84rMEb}V{(8QmZp3aX}3YWXA zGK-4R>FMc#5zr`eyaRO#t7LW?(@&gNN5MxWr=+|a(DRCeDfaO2kWo~egIV@CT5l}0 z8lv#2{x#o~yuQ8tvLjwpyTUc>*RKck?(;XmMhe%) ziRgE_l_@c5{BY@yS2_K)YuA>X%crJw!4OJG;iEApLJXdJH(;D!)&-D;%p@l#Q}J0+ zR##VB4*d*n-J~{aG5EQTN4rDhNcB}i3bD*8o+6ha*2(lJ=k3h1zj&D7B9R~;sc-tkCaM5{mgfNzX%HU zMbpO{BBw{2A1Jxr!n<`}Z=-PVN$8<44K=DfN(~2pE%c3PJebS-m(E zSeTo4?!1Rj!;7ssI^_YOq3|5tTjT%=C24AU8{k%Cv-LikUKOqPT!I(9cUuhqW|uUB zU_bV!)$@iBx*%ONtVX^EJl<#&KM6}$N!wU&WN?5)fg8ZD;RBV#K#oq7unRjy5bTKR zT5!b0#nms;f=|T$`_X2`~TrYoI%)AT6Rb+oU+||vE z*W!hfgM%TUHPla@)es-(uRY)qDsfMd;$i!XfbSl=BQEcJeIW~AVq$Xq+lw$jKU%M& zH(e#h$_6SPF;^Uzqff1^a{XDFwSb#M4s4O%JBoGq-(k3uSM3kJ~ zDrpMJDk`-gjy_cDU%!1jpu79or%#`t(HjAorRm-3y4=$6ANjy*}77*2^z2=`gPK zBVHORCc&|>v3cv`LwC=El$5l$ukQsXCC=K`7GxF&{0^>1GaOzMkT6X6Ueo-UpxfEVW-g4{^c0D3IuYC4l;INYm-) zK#G;hD8u_TIU{3h2^-#*sji}=G;aE)>d0ZB#Byq`z)XfGrVVVXpcqYidwXwxzmOO@ zSp1R7vu7F@Pfp`32}$VP-_YYg zv2KUU#hS&|bkMT81&MgUXR;RD(iZZ%U6+7VzQUwG1LB`<4%f`pKLb7{grQM>V8B)P zK6P+gyCIVK9=mr!8d$I3$;JS7O;B2z6J=SN1yWaaKC zXg)exVdvI$G3wnXnSDYHj(j7EP()d=Tkfw5xH)6mKwiEN6*~w3vg|2!Fp;LZh%96! z3E>OYlFTs%)rVj&DF{)Q4WCh+zjCx`TwpFlk@39Px?;V72Ipvs;q;qgf|xqnQ1{sB zil8?bs_>YYr@#j+it&yf?|F5Xcd;~cqd>0iNXlaw#T)kJPD^2KLV7cf^ZZ?BVDlWf-~B^$-yq{q{e+K^cj(7SpwJ^cy{KA8XVBBRuYZ&0AEprS+Qgu4-qvO3zf zw?2gy(EFZ}0#hOlx%&I}?-9%gv4q2E(a&aGIzu59LfZ`8O)K#sRW9mnd3m{^ho!PI zDd-(=34U->TkfKgl9Fu=aeODX_D)XUZN{oVqIHpSZfzvW`G;%l^Mey}p^kX-L?T(cRNfou-O4=B;q)wMIvP#SShpg)U8-7Pkk-QBNH zQwKskJ2sZ!?TzCoL?RT{tv{5L!3bSvVmxD^Ri)n4*!*X5yNXv|Nb zIiQ%Ifc+8>i0nyIoPrU8;2P8pOhQ|i)v!iHe0)4GJShW1M|+$wV;0OGY)MriztW&6shmSwGxHx1vrd4DvDI;?p&I%g#3KbR7 zaUNW5nm+P9ov;eX=1D+6C3^bV>C^TDVXJ$C??c4R( z*jNZ=8-TT7uwe8$AbV9J^9B3pzO(cM?p9Dxu+Xf>?-HF*u1NdXPZ={l6jQ@HA zYX^@cBtR}J78nk+v;+YOb8~ak4!sWwN={AnJ3ib+<___BbM+8zTbD2M85kNOOI84z zu_A7ta&tMISB9V1*>ORp;P|0P1VlfAxwme;gIR!p;RhN$H<)KgM8o$11|JR#mb-9d zYZWEs=YIluWynK)hn*dnw5%+)lQnfMj;)6nX0hna{$;bb6!3u7yyCu3rY}dAT*qyp9ivUr6!H+ z-_Fq-s{iZe2FNY{WxoH@*3hFs$7i@0D_DF*p}zmyS!E}G+H4n3RjR5hljy|0RaJlmy{4u0s;A!1H2xBf&hLdJ)RK&zaWgH#YI5A{=IU$N)my0 zpdBPMok2igQUAR`K{7J2fHxssBxOY*cfk=5s7RHUXB_V7wkp7Khe$@V(+G+%1SIp}Q2`KT1&vIzq}54b0g zlAwf^B;o^@G}s=#y%`HjNKjHch2o> zbhL1zSl%#j4!{-)4z!3TqMnDbl|_OUV*Q@+*`?1-wK>u8q%TE;2Kp1MnYGd3HF_#O z9c9WJQAbRhaot!KPx>+_wY+*-OZxx zxuI~P$Cri|5uwE(MTlT$=QN#dOoWmS!{@Uh?T!O-%oQcUediK6O-%S5?aXT`p4a6F zvTlD!QN5DA0iV;*Kat~d%}C(6=S-rQ7%9&m_InLKwHmG;1Wjsi5x{T#gN8%Rc}5bo z?WF995e_;BIF+Df%)SyO1NS^rv|Lk!h$1OdTfma^&vOh3Ho9g%D}lyFUX=%}`4ki= zaHG3Nkczt>?z*)+-KB)m(lp-ZZ$or^(qFVi$4eC9ir`!_ILbc*mV%4WZuFj7R(m*O z6aBv))MYyFi9_zM8v3`P<5JUw{LEk~#;lhY*0_A`e}2CB=OkwDlk&~9XusuK;>5~- zM3htupqBJ+#~hUyW3|7#0>_ACL>D~P#hv`E+jyc&DOaYEA8cCb&B&HI zgsx~!O1>x5KGBc@?u-yNM|WJe?e~*8x^au`TB-xtXxlLUAKlcPsA0>k12=Ws^*#oB zj}X5T$#uVx@F>EBHQVl~y@4|)hhAvZc3FKbt&zCSD7zZpe(+(Z!-=UUkLX8*%6T>> zdJI37guR2MsINfih|G@HC zX`e&?8ZAP|NC5YSAjQ?A3SnAo@b8s3;Vfbt|et0L&EV9*Meljn7XRckN^$;O>M;GUD|f)VkZb zuY#o}VjY>yyX`m+wp&ept@%Iq@GzXRf82>$TS5nUJa%cWd#;4*HVBAEVM{n$*NQv5 zTU=64ay|ZoD7b+5juL+rN_9BhTF3K0f5v)@9UWd&wIg`)8-#%Z5;Zx6(;qI zV={Uv=&RYU&)RP83yZs48ZSL@8E<9~T$!2(x*c8F`?Z&9qx;wke#FItO|zDk+a9Lf zh*C;R@dpE8d(#7zBe`Y!R!`k!qM6(PbyqbCsg+-flVQ zFQC6}yXrX{OMw>7M)7w_ju_|S#ZMnlSG#s)j>nZ^kXpwFue)XW@Tm6mVaHsv?#7QA za=5cu{c%i(i9hv*s5f~Z>bGVrQ52Qrfq=_bD?ZwsG#H?|R=)XW?slbdzmngyv0C#8 zb(%3G@O1?X}y(q%g$jhS!>MijGD{~2a+#VT;gg~xz-X}HsBFn zpFw+xt18!?dgLbCZcVeZx&E#xHCZ*@yVrQ8o_h+TZ7epU*sp9Ue5r9p3C?D6F(@mw-?Z3gfdIrkZBX`_gb2 z^gS!LFZy#f{W47T!PQL}|D~-o)!=N6m3g_lpErWXLixL5bMudX>cU>*1@D&?0wT3D zV-mxMnlR(V`3Y}(ah=s329EYOvX|!I;l@JSz)=Cei}Xx~3DjfV5T4t1yiCtI=S)UN zs29z$%Bs=}#fXl=!u(-BTDq$JHp?xCixu$;mz;uPQD^P9=EQxG1R zy`yB?@*gNoZl0B9llro}b} zlG#r7kS|}V=--qX7&Ooyd-D%6UA1w;a$_Zh%~=Vn+w=*Q!6~X?<$51gUMp9X`^tF^ ztF}Hs$c}`@9J{771-?0r{}u%v=Y?ZlOmpTn>pRV)ijgTl!=CB;NwC`|-&H%RWBgyU zG}j-;UG8JH&5yYc?6>N)_iPtAS{|V0Kq95tFX|iOl_CldDficn5x1{u`~rHK@C!TQ z{}Hku2#*Y#mQl`z*M3nE(TefjXQETOpCqzZ-{Ady5#^4G=>hv@+Z%c@>a&J0A zGE$6ICOe5~ix55b!f5Hjy~>3IC@x#Hpl7lGQ|ltP_L zgZfiEA)*XATXY_FP2J^&sc*c#ZcOZe-+5u%K=5ACw+3TeH))0`0*9taxsL44A3AXDB-OZn#sjfc`=z@UCh| zB)CC6Qaq;g4kuDB%Wx5%$GJThAheh}=^+*8PMhIl=Vm;&la=t?r;&!>PREK@#bBIS z(pu_uU-<4w=H>0>P}&XtZzSkd;rxjJ$LwM-GEjCmcE9bVem-pGn}>ql$1j-vzwF0L?b1}HzmBu#j8g3 zDx-MEnWrOpQB!JWl|XEX)rY{m#p?M{A*j?c&l1PcZkQf1VZ#iGjqcrc0{}%y#K(j% zSMLS)`@4ZwM6&@_m9am{-a3yBfVD zk>PyIP)G>UnJSd7gm0Jb6gP|y3@rCv~7g` z@~seM?NN0{N>GQoagp?)_usLO^d|kR0z*gd85%f>ICX!!QzV+S2=3`-2M7Azj_KWLK6m-Y z4nba8>wpGS^7+5)2C6P6NPc-I_Y&uN3l`#T%M@PcWzlOKm||X8$ow@J_=d(-wdU2i z1|KauVYsWquC~;;cXk|CXpIx=-<95jzm%%DLO>1Zx9QQGI9xqJlKOAlMudntGe4D| zs8YgZ=VqG6Jn~9+Py1QI(;_1E83vD-YhBB%ThKUdaoQoOh$HVgdE@|c%PQ|i9w_#- z)dkgbLV5|2!gqUhXE>fCa;Iy}O*E>1-fz{!Og%l#MM+~W$~Wm`hi$CC1AKe9K#MJz z-1Ze@lvlMQx+qY>x7|7pegcEH{I=6j_MB*ks=_KXn=9-vP~?IOMrEa$sgU-!F&W+? zbHlrk23(kB{oe}glp4Jpg6zF{r8jj48|P?^oE^dM?u~^`uM_-W+3cM?1^Id{kD#MN zwpl~B&#)3VcCiw{rLojv0-Ly8x0n0b+7=wk@11m5Y}SS|s0Z92s;a7J81V6dz(q8= z3Y{VV8#&`v%Pm{ON=JHAgBd+fDPo0nz;_Z14tWN1M^oI#3n}(4w`k}#$M>@et8=d@0-5p+#P)9Oz^yNf zV{WziB=fS!E*$;?FH~TrA`r>X*OTT#qP>`B`-JD(QO#s5i&_#bLA-ub=>QIQvS;>4 z80!4`7j08(^N(R{_4c%QB-hn~k_$Y4YhxH_8GVem7rMHP?yz~$zwP88BFL-QaMqgX z@HKU30i8};VRu&a)G6lSEE)TO^54P>>|Tc#BUkHyt_Aa!$R5QrRNO$y7V9vkje9`$ zh7DpDDAnQOz*1{*Jta5IH;GzW#WEKZhDYi#kj~id1?7|3v=2=T-ygJ~vb63!cXzs7 z5|KYX8J1~;7Bz^K+*UA^>AYQvovwP}>Z_2}7ooBKk5U7M^m#sfv3eNh-X_q3iu2)R zK?W!7PbE#r&3HQ4^OE`k2rrj6OZcU2){$-dP{ahNkYZ%b;!8R&Z8|K1gz>rX7?#r& z3Wj>~&Fl&|SsOftK1%?k{NzFY(pXw^eV#av0!Tv>!I*x>Ui|Vu@+BVNuUX1VVVxjq zwIjF`482clX)n(D?ar{}h;m#QN{)41!CAg)urcP6`Y8zioL#y68QHuS?PBXV|iNsg2z( zMp@qKJVajYYY)aRUWxNQiTaw?P!+4hSLVK0x;Dt<`1xFl2{%5hwn(-%~)M8qv_j!Ta@ujseyg`qec(Sd6 zJrl>QHu@2rrD#<5A9wAF%Q>4Mtn>=6%jyIo5br!!D8x_8aTsYuAkxzrnWVI=-Gif7 zW!@-3$J2MMwy+=@$`YGB44Q{+kC3qZBr^HzBY;YHx5fw2$HEU+NpM9aT{~M9O z$u)GY<@Xd{iBco%ZfNrzYHmDqtFKLB6=x5qWFOhFO7$>yRq6j3T-@~vG8#NjN>57h z6XBmaAM%?}{V;+Y92#iV#TJd5qU*UK}o4vZdC>7+Kju6-tA_mh5 z|41IJH$B6$y!^zum{47RZNc<#_r--Y#Ulz;fFk}h-)W%P2sx}Z1jm5}#@}K>y(;TxvZ+1G?do~?Kt4!svX(Bhhc^P<4C05w zS|-d<7N;uJ?+Hq_#f}=WFke`eZ7lzez-_YpXpZ84pp;@0#(4WHo@`mBd~8vnt|+)v z>6jf7IvtGaJT)p}@ou3~Dg>RZ&d`|cBA)V?{uP0ijAdrwrl>|yzdHdQ0Du-PptLD` z!X5Z^UN|}SV^&Mt@`O8MRfuxqPjbc>XkbGf+V>BDW``RXT^?n|4oSn8fz#YB2t3XD zz-ofhskSg>k)gBaiN1n_RVM4vGH!LpH8l=>Tc|ljJLvNJgv{&50oY~RnO8Lz8UC-; z17&Q7*x;{)5gqSdTbuNET!>_!@u=kJ#&is0a{T^4%ox({0>-c1JG+Bpz?9hu4a>7v zSI%MS&~riTUY>sJr4IS-R-ZtQ2LSU(6&d0dCe-N(JrO%`=X||oHNjNp?z&^V8X++$ zW$*OQkh`US=A=CeJVwx2G#9q)*-{82(+n$Jl{(atvV$P_MHo7zw*6 z2yd}`wjQOljxb(t6>=$3P)n+H`c(viU!pm+nt>J)jTJ2$Xdw*_t42G_X2&`rhYJ%- z(~T&4^0EwvPXbS?R~s_G51C*#r>KTHmRdQa%^2V~LZ4J$%UDaxs7<5MX@vR_8f8+u z6oX-FbkbME$&30oyn#|%O@_5Ajw#T$PDzI)z^-9Tv@sRWcO1&^O^`_j5Au(Bq*OX9 zea=*BvU|wX4?;Tg*{LZA+T8?{^xH|7>efBp>WjU;h-T-n3;(02)VY`IcR!HA0q~c* zt?t7Mf4xyUJNS*tojzE?y&1uibw(y!i`{k_LgTwBzmcQVBd}VlNzBaG4hoN(zt1Ig&ikbv)64i1EUmDw%`YF-$?yho$=?i6Qk3a}khTD<&Fd|D6r8hRElr9`Fk*%2)IDMmQ;}xRVa||c)!_!m zf>vijg(V&eFaZl^mW@A{I7yKK8!;P~M5G*uEh~K~++WQT0CKR`qa6oMhvkmVP>Z0` z#X4aApI=8RQEv-~!6q+f#gn6uO9gN)HKUM?H72sM#_QIBAKD*2W+-f^q*({9?ISRY!nX_u z2IxTa#71COW6#6Y5}yl%$}k*0aoKh9bX+cc_O-{>Vh!8g^{j*-8l{lNX*)h8EzQuC zp#h2RyFSmUa~Tb-JXTG00DlbDN31$M-t&{jy15vtdn>v!m zg$_q3ulO)2Vnv?a@aU48mQC>08*NcB99*-k)roRgQKWqTbknJxx05AInvhJ8E@7KT z@Bj@qlkPj1=a!;x35Nh^bhjhR=NOXlJjAf4!{}=ffB_;kne7qm^kLe-AmT90uXJ=R z5qRlgxw)MvrTYQfhRWJVtb`fGGxesVxHQ%lu%d#278<0wgI;GGY~VM8-i$44gtD5A zp|X9yqsMcUyD#|Ck%h8BQlyOk{N1V4)Ef_Y!Ih90G28y7liA+*vaVh4+;D9C_KT|m zEf`xmeQPtd#2Ml){?1jeGGt3!I<2hpou3pMD$sOScC66)z}-rSu+s{|`Xi1IG3B7J zwx;1NCvZc~n@mkezrH#os1?q~QJ3CzqMvxhF1xanY!u{ltU?0Ii}OaeW#=zBg`YK* zKVO3sx<2n6Nud7nBP8ne+lXux={Jdfc;cYA>lbC|oabFss+v`qXug1*u>osk1It2k z3I#PSaouRtOoDR+Fa<2_VUUe#<4`55j^bAQEcbSCzDkRU{Hi*ns;40EuZYZM2Y#hM?3Le8ve2&=08xHD5#Vsv8l^+CaZj3~4r zFFB>9tTK7;oEk}Uz~bf>hnL>bw@-2~`Q&cg7kZ_zzqlyK%io}>Bb{MPDKcbf)_cK` z?D~=f$vyL`v=nPO(?MoK&maKgp4_*lgLX-0#ReB+>v+6i>nNoKtQ?5qU3wJmgaA`Q zPo;}b((Sv!g1%^*GQ_x~1;{Q^AFkQiYb{XrrDsQvx!SWjhV$nn51|XXjetxH1O>ux zv1@a8+CCljZlN5}4|nxmD>&aWW`2NWShz80wgqDAriJ!6;nOL0ky=4mSmd)z zpL(UWzq$c&FA`{D2@{vV7dKw$ktqi$S0Y(kfF@0q6-0(e_o;S&@vUyw6vlC)nyyDZo)rgC;T!1IAt3;lCpqs!JyL!>Qh{Rg{}4AmWHsaGE&ai58KDb z{C85ZIev4ds}LnlBNbY+Vtj>xxJUTT_R{2rn6Z<`8RZ^NF2wGxph0Vz^cDjG^)F`x z^Uoc!diN*rP`TY=Wb)L{JJN1F&E8wKGtb-fmb3%f?N?=^8aAtQjgYe7_nnb8TAhyY z!7+8G{+v(gqW(>6+Nsu4H1_UKh^0?==(`9GX7d}IV!5o|MtDW8GN2X(i?ezn1RcW= zH~ZG889w0Zd0$kI291qf+ZKP%$}<|FilbGW1xh-spsLn)x*MIA5j}9PGXy_5Q0&_1 zdR9tug*=(mWc!v8*xMgkw+$#tDlVGY0&M~;?je3x%=pdl1HfebW~hz8s)R*WCX4bbI%zPi!Z4;qNS$R zlE;c0t_e0GrA(=gQ!wPIsjt*`I_jEs?6Ir-SAJF@iz{s{i1;9%T)8Q!7|&eJ`#U@w%|BZhw%(k%5FHbYpBFDAIcqBQ6U&4m|ux85G(G#CpIh6e&zeVTQ?Siajudu#vo+FdF zZv0$wXaC!ITE{2LG0hfi>4v?$VU{~iiGdSRUGRVfq9?2=OZ!|L)f+lkuGYezxOvL{ z*C!Qpp9dmboMk#`rD$sIF>oB9yLxWRA+M=Xyx@H8jh52ndhI>4r!~u3US5vHZnKQ| zWz+>vStGRWb^tbx|76Y2S+n)Do(62-5aLq3yvHk;CF#+CCX2d2HU*}b8eME#(vC8F zZ)DduGuWQK?iVbhM!IFdb7yN6y4C(N&CuA`T6P#`*ZM+h|7WOH$rM64nxS7{C%kiA4Fv*RWg5>W8=^ldDQor0*B+MU#~~ zFkD7FO1D&xSpOrE5j3jY~8-n?RU~LWeX*GZN_I9v_nL(gCF<|ChcgE;~cN?_ap&!mMN$y$e%gwW<^M5uD%SblKL=@cINkDlR&5k z2-N1xoJKla*KajR?W?SeU3HVhkJ#@1W*+LL#x^eAj8vK?Rq6Jx%WoQgwr^ayn6szM z;+)cfLTakcv_lH~ZB|fQxvf&K&}9LXT}@ng_>@YXyth?mJz$>&{QOW#Nn#xZCk=WY zc@-Rbi4w`@_4|?~BYy@-Yp~#&>Uu=jk@!BQM2Ut2Jf>`joY3F!=kjy?ZKf^f>Ze`t zOZw&9%($s>22Zgh@ct;l6rr^jyWZDBNDq!;M^%k!)ZLJ`AV}G1VX*fT)!3(GdJ;e8 zrqqgR3V&~rHMSK_3P9t*1>|>jRwfglAY=q$Kv!Qrwf{b}dPN`{jZ;BHR)^1NN;3#v zZghmlr3u`J!;!LUPqnB`ub)*qU9&-pCJ^+gO?WDqAU6#gI%Zn2(bSbuEfD=IWzzVu zRa-n6sZ1zglpB%KDpJng$Y|q&kZip)i{-@PWP@zyk3Jafb{>6ZSy#mDcOf~NF>?;ecK-Hds zjX_tsw2m6C-meT#m1i+!tPd1_Ku-FYE*=w=EIkhKfczq&+O4G~MyjqqW>5Dnc?w3V z!w6Ro(K^QVcw+1hVT*o$TS=A#Cz{z$JA|i=YjL~oQxrUU%=YS=H(0r&Zro1^EhiMI zL7`J!{|{3Cu3Zv^Iwa3grqH`5K+W#9V*$u9U?J4ot~H>e`il>tBf4#}19 zCIaTw`5Lsq-RUh@%KRZgNGSYI zyd4uEhFIg8jhV(2_}`_NynB_ZpFd>b@RQ`VteBTR9)0XupA`>ZyUt5;5FFB3xptjzYYmZDyY`rLZ0l9fK%mp&^$TLhU0@*jy zT0pNp^G6PRQ!Zqf8>2HzlRjQIatzF z>hR`h>iZAA--5IeP};jW{k>MfV+YgK^uUWwS}LBc(I-LZ02{mLM}C-#$tu4^sQQX<%qV?ZW!m%`lX`?@pTA>d1;(Gbs%&n6IVjL@g4Dx~HDJJF4Ii;(uy zPDdr<^S^v6j#ZWvhpo3Bi={J6`?57;;5*?BKf@E><2V1F7ivGhdqbq~U3=Stx;E^h zKYMO@p!!q#-$Hup(JLgcJ-K${9D(bF7dkBKIKnN4hA?p9tvy)0e5WIFFwB(|e zYD+Y|Dd(j0EyuqBiH|t@Bxmw;u~-G_=A+0HezgyS^7$^x%kH`GcSny+pXpqyK~gN~ zYe##{bax7lHZj7_7i*~E$3aZ!f)!)B-&*Q^QR&=44$`RY{qTSCcj@rh>ln^B($V0e z=*(lM_6qf7hMMgN1XabG1h8ZAaR49t7=>W9uX`uUkn$$8y`lp@$o9aRpVuToEc6C5 zGkj(zU`7k{cz@m%qu*1FQp_&@bo=6OWM@s###&QrbwQ7b>VB@Uk~m2XWv~T-M3}g6 zuDb_iUaT)FK}V(L;eJ@yzH7|`K28YxD?p6FcZ zCn6+@%u0zXjcqAolYGfN8*f+)U0m=0!(R|H7HF)|3Y0Q(V%%z!B43#@Wo|&SXgG=6 zUF2e2iq>6vcF{!&mF*|2Pn+M8C9IjT^=x30VAUCdB$P29%BOpOA24i9V8%w%_zOws zJJ5ISfIubnhwB+?K16do^Mxt95A5VfrSPDY>eB=R{(#2AeRbG}*%)Fno??5klZ?~) zxv~`lo>C$jd_L-vE6JBHu_kqasWz8Egai3X&FB3Fj$dQ@Xh3;LL0d`s6@(*a^7&V= z>P^&?JPB3|BhiE?WjXNA8?|NDq-7DPe z^KB#2ZrkB^Hw#A%k~r*oLv5%+?=C$oex>l%z_vZ&Mua65|C<*-gRCcE?C{{#q(D|E zL0eYS*;u$pfsXS^+x~%xqFz(F?1Huf7rsSCNJz-s&d(*+#bou{zZ?F&Ck_s0VDhue z=SStzlZWY$_B$1Ak;_Wj!>g|7ymvJ`>1_T~a-sOLa}M12xw-Ad2!M(0!Qrc&#G1pM z+O_?0*t$?%cL$Q*(p|)>rNl%Kb%L~C0ygoN7uXF^m5P}OY*rnJ@Dv5)_~K7x{0Q8y zt#-<P_NTCNW+{GfATIP(G ztxPVKZIViEiszwz!rUbfeAgDE7=F>_>+zY}ze09nmkr!)o-TK>*eqpIIRK-jigk1p zW|)r8ix)7!6Kx*x(dNm)8%YM1^^e`~4p0t?tgi7G)xZNLh;U+lSq2zxyL3Q-uhw{Q|?9m7##7O^(o1Fdr>1D|{h zITrH+W-R~GMv+*C(-3&x`%@pamJB!orU+|!%JqUZd%d=};pTnkeq>3fTJYTG>ncQ# zrdH6@vELX7a~N=reWIAoZkySj;9n^VIxlt6n3K5kXYaSO4~<6$%o=@V$JZ~~_P1Wz zS#h*TJ@9kS z>rM=2=kbOPALFu@9iQw)E$>^5t=JP#)>ZC_Fswh3{MOeUM{mP8MzVmN#%Ol%Tz}4l5D{h#Wlh5F6`>%W6wGCl7zp6Z zfh@@b8b^kHOG^J8aNv}@UY=paHy<{(Dab>bxIm_rrPw^`Ez=zT^kJ_~OdNUi40B$y zMEq_6t?9vQx0@M~nV++7da_%JinvqB$^$|dDPVftK~>r2N=6yUWA7D`$Du#^yrA!R zLz<&#T6o=p%V6r{&r`$yj0BK8`d9!cLI-!g{&DPdJxhFdmZY(t)E)Ol)Rlc{_&V(U z;Y#*!GECZ%Jyp8DNm{jz08RYpZQs)p*YNr2JxLHBK*0~3r5_b$qtBm`c{c_nQv112j;R5}8t;9In5V+)9#@*M$|HC=rR?mZ*c4eSa`M{IVU-o7?_01W1v zDSZWX)lq3@9MC~PZ08?;vnu1&#@Oex@d;Y*BY6JRb%b7=$=40v^%q5W5XAR8whTJY zzMIcC(Hi4pZuWj5XOMO|=NF~wmnn{M?e?gxjc4w={+}n}Q9Y;dW;Ny8J<6MVm1}xO zJ!2Ib_Y82KRzmR z`L2r`ejH8V&2;_(Sc%o2KWIPiezH(XtvhO#uRFj3BabdiK78oLaKh!J1H-}r6wsBt zn^YTpe+}*Q%Fc#iNrf}tqDH#h5eJv573=QV)V`x>X$QskbcIFEg4A1d*|Wz@VdkVb z?V6g$;ibhGTGYN+Uv8!KpB+8+A3og-fH`wrO$k3YHfHtDw#0ewj9A>I=F0Jc^dyTU z6pui!PEmY=8wnUYfw_|o^!;%7E^u*>_MAN}g(1_y(5JpUaWlqj^WZ^m@6{f#K_ z1kn#%i#X+EZ|Nw?>^mcMQ+m(D*1&H_*J2$QL;_(cXQVel_U`V2q+Dl8VP_OcUI)s( zOm2DI4{S&sAGZDkQdT<6zK=-s*r*iis}gb?SEo-x`TctnNaP~a&_G;@4i8&DmVTuk zVsi=Pylt;T-+L!bAR+I*3Mp;H)JMv;q(7)p@sOw1IWn{(%R1Dzorj$Lu4$b|m z_T@1@qKqNE{_&MxduNE4u?E;tv!ynU)-_HX`zPaJw^FaEr`)SNmYh*8evjd_ilVge zw8=ixu+sL-@^OT*NhdVcPZVKWS=#Wvw`BHeo++_sSafTarlR274LMa+zEKt4=Qrs6 z;m{W|!5=?J*_}?GQ8k(lWdp2xh|^XGN3sRzaM@ds)wjln!a|h?S+;$H=%572MOp+#?$%nd%XP6gL%(@6(_%MCsYD@W0$XF@m4}RJ{v;fuxSEd z7OHm$x1Kp2Y7xzK2$fE0JnR>hVz6_d-7vv_LufVJa@oj$?m<2g3r6}{xPQnA_?eJA z$Q@? zF#h~yPDv#xMoQwamI+*jcC`3v(XG#F&0lJbo(`nED4T%;05VTY^MhqA+H$bZJU1`k zZ(JakornO0wO|I%r3EWm6*356qS#N3*dyQ*@Kokw{cfBNKF};hDD^b^Qel^_M*`SX zTEJp_5^ynZMK-VyCLvNw7z)5(+dlh~$2C>PlTIqaKvGO+UXV*^^Had@1??HkH&>%o zR8?=!ODd?!Zj|RLC7gKNS_y##6kwp>1x~T-vOkG?+Kq6u1L9UWasZ!>RKa#2kf{ik z2c;bSwA?eW>>lSS{jH}dp{-4WB-Red3|}uIeR+&CwuD`9yguJm6AAZf*Qz1^M2J~H zXCMg#P%xAoMF_lSjfVBMjwE{e+=5JwY7IX`L+&~K3HwaXe7&|Z)457WyhBBT34wBr zvU5H@DRDjuROUQ)6mXNLc3<}-hSWCH60S1{#~vhXbS0l^R;aWs;iLN*0a@-$_m6iNtwOt&uv7SnD%R-+qYJCya4=kVYlVg z4Ko(3xYUk|MOcf+mio40j3Ts*c}!pX|E=u#abX!_2IAw@?CvJ%NKz_HylfLzu78Jd#~{psQH0gX4Pw;}lmjL2Y;{MX(u)`m# zCmxLW@$I?|^=vWKSQ8_5!ylJEwIOvIdnekQz)<;yc9HHSGjj6d2MCAW$C{n$!_(HI zxsM)$6cw;(@zr`q4r1p2OrA_*K38jp94d=9rJAbyb7J+SQvVjbhu@ev#q?%A&|RU; ziJj)iXCST@JSgMC=kbyzt$1+nX=^CRu>cgRsqyBu=c_lTrZs4F7F?w!Tl(RY%jffw zCTMMS9|o9O*2S93N8e3yCc$x`7zx}|@!(~7&CVoMZT)qw>4g62Wd(ztys$=Bbex(f z`ngOB&GF`WFgWb#d&;`I+qsbv@$@p?@;Exjin6;m%^K}`07&Efq;&B9bFzOd3F*8F z?!R3j-*b3?NoYFiO*lKdDE9cPjuU0ql1@LOKV8&HiaGb%i+BAdm4?gdw#`7Zu!!9G zid5e5%JxaI*$x>DA_}Yp*1Xd`^hb+#^f%e1!jH=3$Sq^1DrjOqtwS{X-12Hogn?Kp zpXTOf~aTkaO$2=8LO?GDQYK7TYb&|~%vh`{NF z7JkQbd;8+|7}}&w22fkxw)|b7e6)I|rKe8;8X}jkPBMlzrhG@2XsGuM-qZR@=1%s* zA?=3kBI&Aexv_m`-P`3D(RTHpHUs7i&3#|UW#&$2D7EJPoT7lR$qG}6e))0neq8w@ zVICd0`*%k>ko7&N>q*^|H?6xo^#_q6C!nBuobK>&x%6>{;JMIxtZ&daV(R%v`fvK8 zT6%swUa%NYIXO99OWC{EVfG&t%K|8=sbAGNf`Ppb$Fx0kalM5h;UbH~Y(lIu~}8rP0#l)b&ygH7?) z(_2^ztIbep^rq!MRVd#EHsNxRIxc1AlE6rSQ48L0xE#jhU;B0u^|oT^v2i)c_4+CS;){|s;Y8zd0o{%FQre`u%Hd04-CVA?gMih3tUzGpxg@gv%lW+@Ko z%J{S~){#3CZU4w=P1c`e@@(fM^%JpV?)Su^uX>A{i`RRbI+gAulCR@)$?)>j?|$I|Hk|E;oHLcN z#^P_MKHAnKsD?^yrIh_<@(>TR`JoifvYpSHE|IyN;>#xL5yk4o!-Hulu_C2UAki`>vfR{}Jhc+INpFK2yRNm{^eNW`3*+qMB&DmQ zr7B@7kLPn>Y`eiI8XJ|wWk#woQ`FRxl(HjluP&g9+>O=S$-aete@GGvIUqBVLOw?H z+gfV$L-zM@L>@K0QtPcwGV;9}9;d>c;G=jP+`>4?#65SVHe0Qg4;GPA!kqu_}I7&aEN-mb&zcuv7p1|ii5Jj0+b zel@sn?I^0i5)SvUG|+VkqDTlq)8%nM=d&U!eiu|k0xzO0g);I67XgdN)fvIpJTkr}Oj7m2-PJT-4kKpAn~e z^p|%~qLDPV;6Kx&BEhe@;c~0icRO>uXlwXrXxd}zFw#;ZfBzN_6eQzv8bU!Grch(p z6yZPwhSZE`bQ|E40Ja7b10nBBp2$7cGnQ8Zh@kphg&TeJ)XAp#OtN{!e-|rirp&}I z1C$2hkRPD?B(5LYFnHt(I>f!!11SmU2TuQ)TAGx)^rM*^Nlz82wr7RD-6hi5WY&eLEyt=>@>&|4>ZFPLJ+bpw9=> zQ=t{(Z=a24@Gh`psmQnWQ`&#wbg%fp8QXY^-CG2Vk$x*Bf(jt&D{=5FopupAbv0X! z^erW;=xGu-%^l&k;I5a3bRkPboN2P2>oi247b|F+JCF3*6{=;`(qKKXwR-f(Aph-$ zbx$ghnIq%E86V+sxJKP3XK>lqYfE7hEZ!0ft*=p+e)JqaW)ySD3P zD_@E`zee8%e80q6V#tdcho9?OqQHf@TI$d?l`D$BI3fuv2SN+PaT+`X!o`|+bJ;JS zRMc1unNV$XHQJ4zW~Ln8@@UiO4jq#{YDKMD`||ojvszgvi>1^Avfsliq>Dw*#MU!T z2i#sNNpy#IkHj~ELeh9wi&heM=ft0TR)$+UoQ>HpU(vrtl&Od01tT2b2J#dL1fRO#Lv}GPLzqRVP5pBOoAuck3-;c<(XyG?$V&fvn&! zO2%n#Rkl9jtOaX+?yl00CR| zZZBCS6XFK$SF8zC;KYpEMibD%%noL!^9@*a;C{fE&6$o}OE8lguNYV|Qf@9W;>a4Q z>kCh;k}-~R;G~iyYs={^k>y)02|1Rhs^&%AAZFsW;v)cUZ|xJk3j`NsmPWi~|H6bB zGPRxiQz$U7Q(*1fZPFA|V4nSdy#PlMAktD(BM?T+`}-y+*s?U2O3Hu+{)qYDFjobE+%d@p-2Q^zJUuTrniW>6&( zL{yTY%0*R8$6_`pBZQ*UD6@~ZaASx2#dD?(!~MFTVc`W;RZTRFUe&jl?r%cl$v9of z6D=E#P`9tdH?-KEO@|3gG>c8=Lf*aZzYi`d@7M8b(3-K^t_|Y1v<&Ut5J{r= z`E_?bzml!4&t|#;T1dxGT#c+1jOYe)DS26*712Zu&nCNQpAD%tF1sQp@mF)LxH}#% zk3vmp29e3%`DWA}f8>`l5LIja050J=(a!&A>@DM>{GzU5R0I(e=?0bV?h@$+>23t1 zyHUCkkZzC;Dd~_Lkdp519ENV*GynU!-w)5H#~&XCoVnthz1LoA?Q@+utqa^5ytDY! zc#9*xWw0&@8~=Mgn0_l*t=opKq@s2sI1%6>%#gQOUjMJoMV8BMIiPf+H(Sz^^*o}n zdos-Iyw^ap>6*r0d_e7&f<#82Huq6D55r^(Dgg6Sn;_nXVy$mZMxcTx5=A7v&Qx zLN0-!+=xl3P82Vc9z1HXl5Z4{a@Fx!3iIq>E+rLKzB^jVA9J?X+iT!DR&6QDWwRK> zFaSnh(X*G}qp1IE%K_1xi(la20kLt3She#eUna>y%(^A@wZ|mV`}cI?1#0Y5)_2dP z*_3KLIa0KE5s|KH%*WN$D`xw`pBu!bB*<^Su4nawIo3g!LMR4ks`2@0?`&WyfeHXm zR-06f_{Lm9OR;1#X7)C1t0qkv3RU>$YOR7x9=4AJ;`{rm)%HP5m>AY4U1uz=exU#T z?e@-*)wTKAmoS`=E)*)PlF8myHy6|05|`S2cYXxhq==P22+?G`!I1S1LnRhMG@9_p zj_kMnW7n(}TtvK$9yBsN)4!)>P9c^GsrVe`W|oK@L~NZfGNso>i9t6sU$>N>cI= zD^w=N#ixagS}>eus@Cfe>w(Gvev*n=^rSHvZY()p7uGDy#bB;DH_kRvqvXNR%O-wy z#n4Z|pqIU{A@l)X+19wh6FYMdFJ@zsFc( z*Z)ZCz1K7xfA7Waa1enbM9a?|^DHFBar;?}5!?MX!C0FRZj;a*1(tyiM&UpdqD51$ z&wZBh=HK(L!~Z(&8}mN<$w`pQ6L!Nu=CyDfNShDoe1K+N8ZuU`?VG%#=8n10yJg#{ z5<>Gm6A{W_MUIEn%c99ti_P_qcTZV##$~mWQ_#FoQ>#iI$}TDTdwpb&Lc_pRpskAY znuIrc)Z)YV`9Q^;lW$~chvA3IB8xb%7G6wEO^%8@M&SIZWOTvF?5!Q!;ra+GK3Xkw zG)9mA@;yP6A459ofA@Dc-c?8;?Ong|Ih8B&S0(4aT}K#T6Mo$8`reroSq7>7n~qLA zn>^@CZrrIASV!Sc+0KP~8-25d9(dh%o`}5bXnDAhJZpT^FBnW0t!Hwpbi6k|a7E7G zU+C3XW?Z|a6s}pjWp+PEN#gZlLYDR)|2*E{4Sz1oi2+WvMnrX5c}VWOxhX@$4!}b$@q*U*olm# zU(S+jo;hGXe?;N=${jbpU-O7HwZK8=BdY7sq9RDIbipKfhcKMH(LOyATa($6bJeKG z4{B=FPNzV2?~N*B>y%^T;;6LS+L&rlfv7lG5TKBL&+;d$^1ESfUdbCw?7oyi2G_&+ z7>0qqYO$d`)^sWUpo;y4(K^mA+%{dN9AmkbqICx|qV`H}54GzNrH$t+e6X=`@`~xy zO2zDD$0elVx{rQ7Gd15-5~O;WHgx1;Ll%+ZT3^>`s5DbWos!<-4IoDuJ0*R=+nWJ* zZLfRHo-eFa1yygFH^m_sRDuV{%PW(JLDWuztDMIOyp;M`>%idPa1wfWg@mqRWqZnD z|7tttIKip76m&Yev|en~D7(C-4m+j|J*I`fDPGq>$@r#t9L@V8fnG>i`NyM`fAoKe z?f+Jn)mtWRLr+KwAd{cV%>nNTYIwtpv@u&skw$aD3@Zq*_ueo4P%}{1)y3(n?QL}T zbJ!kloRMnUw4{oMf&;}4XZDC^>(-ckqS#Zk&^(M7gniicR4c*pUu1Pc<0ywS_$70a z37B=ej*AVt2FPf`{j;)|dn~RX6t&j=BFa48XNclG zlz~BrSQ^y0U%Mn@Oq9^@Um*>fZNJgyjnw8WS#|ZgGGAr8pdd3|{?-+!@shkm22oP+ zXS{(@=&Wfj7?hlMBDp7^EQ?)X|8waif6tQ2Xl(Fen!!6$(CZ?VY&&VrZAAk~m^)an z)bDMZEu+4>4d(3y*?Y^bn8>USo3Wd3j0>>uU@WEH+0`8?beeL2IjWi^ zxw|_54F3CcwRRTa%RW|lex-M^Jrei3*AL-XA$<@^caj@ zOz9+_51~uN1?JMYFOW_#r4Ln+l)`>%CZFD89<|rA`;ax@iO2HRn(@Y*AN8;PzyeWy z+5@puvG+#r$~s`CgA>_6MUgiTw2@TgQ|!q73d;}(7UU_*TG)g4#b&)}x}c=1zEF&i zJ6VWIvkYduzMH7n=VmC@mfF>iR~Bm*EG3pD+Tc?~1;& z-u7aQFl2c;N#W3zlB(&YYjXfClq$~4nL3EtH9oebpI<`F|0VTye_SMF9rkcw!gV@u zVt2ipHsZjO;eB_lksAQ7rhG;(P;di|9~H#I{VT%k#8%-=7MU% z!b(0ySt&EzS>l8-qa(lN;0gT}b7tJV!=1wB^g2JE3salJ>~C{eyN+)>lEYwTd+1DJ zhP&qqd2%F@`LNapIaM58Chk#XjL>}}kWISOt17xVJ5b7tcKezy^G%fIH7J`OBg`}{ zULfGsq~K){bU%pFoen_&9%vNK?Zjz!RTrCuT99LoG2@)90I5v5aywjB7O zMc6X#`ATvUc{AgWPRDUOtlq4`*wRF`&w3RuHdV}Uk-<;q+7i>a(HKYZP8!b_*?#r$gtFYJT@Z3h0Ui1D9J;!; z&a|8eU2`t>PNCJGjmcxF$EL`_K-bqG=$A*(r9F-nZp3WlZ=R5{ zPbqXF4`TWaYN)}_kVjUyv{>lJHWj-LY0P@J4Nm^dr&XpTgokepE=84Twx&KWef^#Y z8zzDYpHU)>{7sc<7kxjY7s9{ppZ8?jpEy$$4x1Xg@r-i3B8woOUtMXoXeczU1{5%?+o>}ll4X2q zM#iyF8Tk`_pO0p3W~P>t^3?71HX7%w)0oWUu~doL*lhJ30;=L%v3@BX`CPA3T>OxZ z&Vj&E-%uIFvv|_TjeT)s>33gw#3i0`Om=M8#f%mOq0`rGju%-HKhqA1tMx?R-czM^ zlO%js&z+k=9dbFHo;!ir-G{su#@VD*B@|>rUu`yljZpO_(s}B7mKr8x2(WSkE*?oJH0o%S$YIZfX^@ z2?m?LT4$koIU=wWQXJoVyyv9&?=i;<<}T1ZS=?_^&ynvC(ByqHMYH66ZOLHHeyGj8 z{ZNr{wNb7-QGwAlA#IZUu(UrBRqi^jD&erD)fA~Mx8Q_o`EY|9U5fP2g-`>1)BS`X zE_z#KLnDl;jk1ilJkEPqChE%KHRDo8kxNnFM^&sr=@VNUj$Haw4tIEE0vM}L; zI8h-@Z$f$xp5RJ}bc)n*OC_vQ3$3@fQto>klIy_1m4FbMGgNO3iSc3nE|s$8`w=#j z0{Oe~)ORtXclF>CF6q!an!zOTaqeTND)*t)^U958k`|xA0Bx)FBogFgpMMgYZ&1Tu zSO0XfIDM^u*E3wnG%2;{mCPJ>SZjwHHk!Q>lP0=*T_*K=yxHSz#1_=dqgd?g);kEM z-13bO!yRu*xt8rGw?p%>f{$x^!A2*X>uE`{kFz0fxl(aGxlFdm9=b#c9v~{Ota{aR ztiF@I`_^V=n`4sFKE%^qAoWU? z8bId4!s__0BfHC#cW>(zwvVTC&V;#%RJ*=6s~?JD>j^H(5K+!9q0u{$maASLPazSSX;+b1T_WIem z<;@T!Bu>sNaTjpNh8DK5ifoP%{=MZx6BLezzQ)5?s^rtl9b%Zx6t^N>m|VyjqPCeR z(TkTn3jI7|BVsN4SDdrYT{Wl>sg@d6A*GPXknPyzuD>%C_<5hcfoTJyUS}(_dEGr* zlrvYC4sxhqDi5hQ5-<>6Ouy06BJTZgl8Nmr=VU0HAZ@$(!gHFV+Vs?+PDlRg3h7iX zNrnO{Zvu<`bT<^dOt|4p($2@q;W#@RpROR1AjO9?d*!S1x<~s>d8vn{hjyXUM;lUW zB1kKYitA`tJgLLt;`7}f9fB!7e2$E<+^6~mvC?T==f&HolV8;GVjBYqz9{70XtL(CctQiw&#C7H6;j#eXkV`(Y7IC$rZJcaEW|NUjGK2E##uG z<|I9WeOA4;bir6t+X6YjUs;(&*HPtrXa!afeId3y)BH_|CEL3vFKy4=oETGXr`T>w zf2I7k77kZtuzCld+>KS0IEv`P&n#AJl0160neJy#fQxc`@JFa6>*{GRs>wfvjDAEC zQ!VquM3U~Q&;lb}o0s9?p{089eyv<_5KlKrWm8kF_q%^EynPr34XU5k^mjM^5>(8u zTuCcnb0+boQ(JAN*1`eA}>SCspjIA9>g50x60G?^XJ< zv!D5zW4Uv9f4oOgiacv>ei!DW&9b?>r7F6Xvvim;Ngqs(98-0m#5yXUdfXLCr8%5# z()-O@F3FUNI%h#e(fnbGxYMfl-|vc`a%6>7m0w5>LP*BP49E zVXJYi)=ohudjXs;T=$EbUI7`_1(La`gV7HS3JASmUiLu$;{DYmZW*w4V$f4K8 z>BSE%P1(b%YwdfwvuRiUQG_tcU#&371KC+!}J!&Zv z#(Xs^SUl-rx#e5Ez-Oeawu?;<+npFXsI|hs_s3V^esM|pP4cv;n>%`CngHUF``Jv} z!@pnGC)~(~Zrz!XfUAMl&bKj3@`5-=-P>l#j!0@t9bG+SFXOP>Q=oy>{x*XV=2ULd zS8Mt(xfs)rI#%_5yhY_1q0TV0{!XRP!%_B$viu$^jf}Zl#@{kcuXM3x%{j3zGsLz{ zicV>%3DzOd73X>qXS z6CsxLrY8n|1&jRRx`T<6XOaCgmr#qD;EIIs%D~{9MX|-Bpr9Z`WaNnU1wV`!NC1U2 zO?dJTsu!KV$;G>uZ<6XGk|QND52hoM=~jsqM+|Qkf*J%#NxdqGU@pe9b^Ix6X?5cj zE>2UA)u_^OEQC=lEI-NYyjfn=^otpOB~v{2dtpF^N;YyAyn||%plMTUI~r>1R+pDu zluTQ*wSM7lJM)r;uF6<`QQpX*9|~2e!64HroH-JSH+|}jZD0DMfs=wl&ir`xpoL|5H4D#V%u3!ym(ni4I!jcX8@Z$F8fgFk;_zBc&X34)74$ zrqdv54L?45U$zJKSpT6gpjj@@!Nsv|t*UbBE+-4NM&E;ek zo_je>2^Ei{7|s-w)zu{~DS2pAq11zz*I*D5dddqidw6&R1_gz|C$oM&ieUga4*vG* zAtMn6GEKg|b*DYJ3ZO$e;>Z178ndaw%MUqw_r^~4QuoCDx*DRdKOA)s6FgFi;j0>x z&=K~<@^Rr_fFLImz1UB@KUKzd*Nz$ymkjFxX}5&g3TiU4of$U2Gf^tom3m`v>0-y0 zX3rzIEljZ~^udsYfVD_L_wjI$8imb`Vf{9PI`+NYW_}x^L-_x=DV;;4LP7h2-kFND zzCuU>3f#kK*`V!1@st5+feBsV9EqolIbH1D_SJn1wMKZZincLiV_(PCupX!~xG=lR z=*EeD7S zQrMoWsl}S5{QIZmd3hu+BlD7k-}P?XOrt~7n7HS#^it2?84KMM=jo%wh{V~3WOR<%NxAa7JkU!RoU_25Gsg?~Ume}BKx z<+VxR0)Sid9ySv+d<JGk-#0c)!^6X+q@_Eir;~pEqyP&v ziazDPghZ2cS^ajrIyQ3Fqo$$RY2wG_kQa^nNsqK27l#@Annb3?d?;Dai|e=5WGDFK zGvwFyXFJO8-@jMF4g$xFCg%0;FnkM6unvi-`8+~8XwLpR)1wXa%EQxiY6pvvdgRUu1A-q{&E{OGvRhr!0q&g*-} zgMop8kCyvN{EaO~?!4Zcz_j4`!`@YU_W%{}c}hykgQeCq^>Xbh^C7~!yE}`iG6DnN zYf1!m^TBKrNVN506HeiHe`gS?<$N6@3ec2#qZ0)sY=C@QTc>3~Jb!SSOviUOVp~g%en88M~Bk5$6Gv{WMpN-Vq&P@zxQX-YeM)} zuEXu<1N*XHfmfC5y|~#CxBMT>bJ8Ahr7nOojUS!+X5r2h?pyjQTJAj8?D9YQv7F za#UD)&90ljDREmS;j#Y;V9Ky&wn(dnN+K_o%jtNs4ek6gn6#cCA=u3|7Qm_uLr#3jZ6e+16&i(FDc!P)`6x7c8a^<3-r%Zk| zHF%`m*asl=f6D|UettoG9%2lGVcmXgpaQMdpFhP8Ui`21YAXOHUQ^TEW_Z&1KDBj9 zcx~bG<$yS_OW`(srwfVU$B!Soqv3~oc*6<+1iXMB7VhtXpN4DQ-?naQlS<5G>jla3 zeQ}ZVyNG&{Y=ofQ*RWtKhsTsqV9D21p?BI74NPzFnLr`|Z>v;=c6~idC2m}jg7{=q%3m z25b8hV$&|>KFvQQq3t2_c9t*krs|`MutE2-ZgWYYe!R^El|vqj^F5GOaJO;WPf{6s zi&48Hoh)&D_d9(5lON;SzqCi`eIN#NknylB9BYU3@>|tup2kc|Brj`FQL}}w{ z1gDl91SvdL_?#xWQoIgvtnordSJ@%EGX<0$K|L+ktInoc)LQjI|GO68<61pU+oWYrpM(6v2w{}9IlCaOAeV6)e>sbL~6xMKgY zAwN-9yk{q$fG1;ESR*zru%8BOqI9-1-QzXxLJ-mOf)r0d4KJ(i%T0eGD-ByyL9UhA zWh=t~v7qM<&-1;UlD(`l6Oyzc2@8v&nQd_XPBKTerbWs>rmXOuo+;}yLpFku#{&~s z^kECW0JHDAa89AedS*Wo8b=G~N2-`@b=;hHtDhc^_BmxW86dlui)q) zxt8zK9}*TCsWq=pW$MUABx1#5{=3KfH`A`|9h)=G5@mF`uAv`uM9C@9Gp`%pi2I}S zAsc`CInY@W*kk;3F<|_adHfUmb$fcvNH)?WNC<5FjBzgu9--r(G5bo-(V zQ`mjpZX0)3AZX-d!qnY!X>NTTGt3Q<hEM{WIb9T9$qobe&zoreGM$oqn% z<(0^n4XPQ81Wahvfn9k{6w?9H6%Cu?#{H|Y<~H~oUzkbf_4j(Z$FIlH7zj{NihR(U zQR6Mi=3}dVwR})rAX#AbrNot`9a(ZkCXsBDufZiL9)o#YV1EIGa$SjsVNILL zLbKna8FqhGq=_6}UDOirBJ*y1{O*E#yyc|$iMLHq{#JgA!dzjPq^mtHW8r8&1gu_d zeXM47dRuFSo%5|@q690fOut<=`Eb|)ljRgKV9Jc0xAD7M{L9^%_woOjKS|R&Jno(> zdqD*~&vrzNj8dJJE&j?DcHZ0M9V5F{+<$6;Ymk)MV}<^C_c{7Py!(S(SvS%Ogo(mrv+4s7PM&^5cJ7$kfyZe)zy# zVZQ}QN1#U`kG+LizSNCth;)$k1TDhhkUscCTVL{@*2ea+5zgQApvFuXt}k&|Hz$5-wZ!_Qe%IZ^dCMZnnPcnzR$@q8CgXNi;4}8_a$8S# zMt-u7Pe0GT(NX8Dz?+sdqZ-Axh2=S2s1ZAyH{@+Z%hBAKgd^%{5gJQ3aXXTdFso&e`8iPhy(|?y)znnkC4)dMk4UeX_Of)ox{c)6l9~rQ|y+5S>F0ts_~18kueI8 zmnw&K38Ytq-GFKUzD5NYq=vpnP`+$ZwZ-UrXx-iD`q{~?9F%Dr$z}tIt5B$- zw)Py(fI*G9e9$kLWOV)j0b2VqN|_}gna`PW<1rVYG*iC}My_D%(lRn9N4Jxu8tj&1 z6o8gIfBu|;it6NM|Cd)*GvL~7S8J$K6?y|3{qX~>OFbs*8E#7*w)IXY8`And=W6hy z{7s-A$oNpERTC^7PnEzw98D%X{O8*sO=?D za>4GwfMediA(s?B#W8n3K2Df%p+v#&JMQ|uF^ic(ykTFjD+SrD2Wrywi0L+yzlu~P z%hykHEG(MBxaud~a?OcoR(_ld^L{#7gfShOsysSTLfAPcK;1~d`y=(vBL3eG^NzM| zI)j>X>e;N~%zC?+PCWvrlC)dOBZcV{)?5F97StYB}yi{qf_+F_?`SFx#2FHy}bpBi;MB2BmfRtOjnSIhP*agXmB9rca4OQdPP>~HCOxIdkWp3sjRK7v7`-| z>GN}QC$XCJeg`QCvKZy{YjYq5(TI7zgDmsv`UpgM>uUCw=?XMLj!+O|r+cNpK7kF> zfI2MJ_(h5?c&3!{H38^BF)>89B@b$Fb`Y7~-rfk6+T{ncH6Z|PRJ67KMiQ`F%v8P` z9UXPv`S(Ok4R*=Q&(Lkn6`!1}$>i;O+<_cPDwukIdzsWI%)^tcoG0M`1U=NJr>93Q zODN+O+D`WM?93U2e2Sn~UA0WHD^J1aAcwB&`}+f1Luqg%0`j@Xnrj?H z2oUq(xs|qyd=fFF@}{N~Po6xnUT(_(Rz;@WwznKyL{7 z+^~SeHa0OCAYoQ7O~5nojy*WAZgM^R8?PwTf3`c$>vlw}rKKe%F3#$CW>Kb250Ho$ zyc4L{AG9V%3ym{}uKHewoN)UAXlD>~L8y3fsi`uc?GJ5#6iT4iFo&)6EOlTjGWJs%L6ZUZ<`Rnj0oXP(EgtOPqCqJB%acS0 zwhMr3JyAih&MzkqQQ3=Io2f-bd!G#D#Uqo1p{ zi|H_=!AtxC6fTfsGr%KAX^+bCd*FSBghr#Gfr5f^b#s&0Xa~|~r%54-kP`u3kAU1l z!)N&lr2JF+tw91XXqJ|i3vSCk-90@X^=5ZhFz3U$gu6R$+xB}`+tzb+;01t^u2!n< zvHUo1*Dh~sTk#@`n=L}wTz|w+qJnPAA~PhmG=kP;cVnKDP@U`8cRTxpec6RO&@DG% z*GWj|ExU=XMw5hZ=SILo%e8{+%8PK*?EzX;6wB53=aIeZj3segDw3B4-KUT0R=R2S zUaU3kpey2lj{Kw7TTSPEqvH!<`N!nmes%SYWQ&}p;@Jku)J+z<_G`r03ssp;I|xnK zgohfTkV}y+eKZoKZ*pE~jzQt#(P^}HYe$XKD+4q((;uG46@RE?<@iuvq&Dq#S zgdYX&Z_e{Tc+^^s2a^hVDwS(E|Os_dHFc7iHd3Vi6K}<}nF|idGjC-KKAIXwtiv%DM3;VVV`T!4U3siEW zr4|nac0VRL@p5t zcc1{&p8y2LBqADkykCAypj{u8B-!A#> zEh%BMTM__9Bt%6~P+Upr4X`xtK;?!*J_u5F*Mk{0K}CQlJ?1rG#~vmjk*DPnL9a7D z)W|s|5^4D>k)?Qt1drz#sc*d0?qyh5tYf0S6#K+3@U^*#%e*($N&wp}M|^TcGGxbF z=$0u+-%fFJwh4(!OahbO0Cfj3So{r%p?@Xrfi_z>1gm{hlrhe3D=M$9qnjElh5Ez&*^Rd2vbj zidr$#^mwHMps$=98UWz6^%!9)5I`8@5^!e@4e1$3k`tlFhg<09t-+MR4E`igzC&7C z1OW7?6qvoEq|BGwmj_-hm{$bg*r$Mslv7f|{YKB)h?a?o3K*bYK{^9lfwB0 zS5fGi;#ccMD)$O6@#_ch^ksZ}eAmPMEu5`bF17H&TMPmM>B-5-Y)~C#Y~Xm)(9*tp z`*uZfQW+d#cfOt(9_4IonBYMhJr}RbBeM?w=Wv7X%?mU%3O2T}d1EhFRR}tn`Fd{@ zC~KgAl$0cDWzxTYpDSn24-9v>eGcj*Flm<;dqEKcU$b#IU%w4lmu9&-bl;#Dl?w=# zn%O~M{}OQ95(9AE0tFr1Y|lKM8!L;h^=#u|-F(aAYruB$Bx6p%sGW?-GAE$qBIM`i zgF4CPbK?w%ohWEtC=}M{qC&-i=8#ZR3y+8Zqp}5|kGws8}gS!A}qy#yL*kri^HpoE!?Sl-~ zA33UVXmp1FXg~n)k%*&siHZt*+nU(}L8O3y06;DQGA$4pGS^k#Q{##LZg2NXVGVAo z0KBTtBt{Tkymux`@j%3%KRm42CxSs?HSK!?w&~*Fzz*pW0fZ3&ga_Sp)IC7e8eotE z<_iGlC$W>Br0JT;Yggte#5Xtri~lFMbQW>O<4lmZ}urT4&nU5qLJFK`2SZYH=Wm7VDy` zfQ->4ygBH^$5co(nC-=3Oh)`y@|T*3iXcx0VP1P?SXps{04WlM@35i3wy^GbQ?;oG z_fJ&hwP`7lkS9VKx^YiQM>T1<1A|N2x*xCXcx3#k^hAIlc5P=760(c}~ zP#B;lEV?Zp-uv4E;*X3@`qQW@cnLRNrBFc`)Ly_MTfQBRF}ul+!{r3%yk&z}V9>4& z1?3Vnbh?1{++A$u25D~A_x;_{+o>XD@}rhh1wbBSD)!;!82r~L_#>dnTd#ERng`6Z z9>zOrz=Zwz^8s7@9UWcx+M4m8d5zn`Mt2w%Tx)^LmCNA!tvis_VVDi?8-xSx@Bt)y zt42M)%N{lY2=;V7mlC;^>yrUyX*s!{m6iW)ALvrwR+;u=!VhJj2HFylk!YX`1lm;+ zQG{w&OH8hM&n7DL(hd#|*z~qk3gkN0Vc%n8MXaqENMn#ezZe7{yjca0P%@zfezCmV z;BVj;2cW*;c}hd`XVX!S)f?1|G|)N)RVQN7ijti@{`lA)cxWHtEega2pfpP6u{wtn?2vZ z{{daDzZpN_#sZ*mUnyl1?bo_d7;sSm8K=LTi>FpB(DOy=1dp-~pVc6Mm|z z)e!oH%pX6n0c-`pA8v?@iHWH{uWM*XhRYv7RBB8huaBDcnE(W}mWP~fj}-bnKD6^o zvr{z9fG5{(Y8yhWBB|d!k%Kh*5`g#?t$JT4+ zLd9b;1u~jJujxmQXh=2ap~5?6Mn*Z8rnhT>M}2+8-}ONhJyd^t!I zhCO*k+w=?N0oRw*up(0Ky&z2I7^1>@4y-DNsz5>t)uf1sZFPa%Dh`3;?bEf6lt;C=+i4G^F`qUeGf zHmg>kn@g>bPQ!j-17Hjg?hw#LTk<}Y2QvYfOrAlzZ(vBsDX0o3KpFvJq6u{$0#bm* zYJ%p#l^;~NQ(p|-g#r*{*y5{gw?}Xv!u$Sw`fPt1U0j*Reg(m4YY>#r?7@vdzQ^UI zdx(VIvIDW7uiw(Sr(omL3KGhh^7ET#Iy$Qy%2I@QerRRSDHv;OXHw}nk!)mWX@w<1 z7^X^+lcmHjkzIntx116GS@V7F)$A+cQH>16sdajC6F>I_+UB-=?{Fxsfu)t5w{i>H z)`NQOUY^PyoqPBRXF>S|GeGyW8g$|`gKR7;LgCgMXrX%xkwryC0mo5EO)Y48Kfk>E zFc(r-SQr8rHCC8d!*-@u-{2tVF9*%dX~TPqK0bm82??g3KgTB|a8|@|x*h3GmFqyl zu&9fQi&x=BM{ce^NJ-$FDZ03Df?ZEcO-+?_bp^JXlb8*LyU>N@TwPtuK2DB}b%F#i z9(aYYzGjB&_6l7&lIXO8uHa39&A-r{~ zqod;itO%HAtMzPkkReMS(5gI6TWG%RehcMervVEVK}^id%(xZuaMw*+S=k+|J zxxT*U;NoKA;gJ#%dE(nHtnQ-^XaI0ILxTcFtz49qG3Yfa#Kt-H_V#*J^8uGK5jlag zHBeEp@$mBY`9N9#v-8zgNiv@w;o&dY*w~87%DU@pz`opssIEZ%7nR7Us9=G4sQvxB zXNoh+j-xN7APlqySkQoF`9@EM)w2*#pXYkh@aBJ=jf$owk2#uzsNe_iF(Pi;pEWh{ zfV?QH0xNwxucakDE-r2csBhnP@O3y=aPbM0!LeuS)xGcD{=TKHZFJOrEV(3)`|;a4 zoA|`UUbt5R+PgH2jGGnBN5%kH)+=6p-P!_Am(Gd3eS=?5HNFm0QdgI-w4?R>u= zJjf#@9UasU9v-{^G}edHjPR@WEdeL3nm^Ig(_62w`wG^D%rr!|k-ZMt-Pw5y>|UTX zz`ih`GYkfoL*8VyGYAO72p}ln77}Qp16t9seF|Ts0d5-*k*rpLcO5LcW zkMCZCMJR)5+%dol&!%|^N+za2&>IcxVsCx(_+KZq6|V8+^D{y<|2J7eC{y#I5esi-bG?P0QA zgXfI~#YR`76h{mYx#KoB(OQMR%H-AB9c*RY4<|(EiCjNgw%gqQM%qXS-)X~PGOcpk zo2~@&5Ze~tn;k#feDl+T#{o}di@L5mGD5qD4Vh?{i26eP*NRLb%tH5>Q^@6uAcnT9 z6N3%&+ag_8=J5iB)p9rVI6qM&j~aHKvxgAk`we!-nu@~G!ldi^h3;m*TljYwq>pz* z%?g=Q7|7Zg2;VLVeej>}iOX7!rtTP;J<`|rI~~!)yZ(A8+fmoWkP-_~T1NPcByO5+T_O~DUOo;P}>(S-hcT*cXj2C z+^98unr9g%&}M`DkY@Xb1?}brx#7O9(fV%EsqB|ms0pNfCHAi|mvBu0FKP);Tq=~j2}_-?TLvmh<`(-_}KUaY>n#Byhb%mBlYIL4Pel_ z1M8*_cqaSP(-}NHN2Cl97&yO2*=HAsmDF|yovE>?;mbg$%Z;%9@6NxW2E`<)jf2ky_uzYphr)Sct~ zA(soEnBL>vaHrgT!g6Nl))GJJ)gjqZ=Pt_=5Dk*nW#tl2Tw7tdh{5)dW@96urm)33 zR#|p;`5|Y;`lKb=67xr`Wxj?4*Sslb7w4xpX?`7>&c-ofit^+CI2j3f&Uf0)36a1` zFHv(sJ-t@OzRj#VSMkN~Of-f9N9@wfOdjR=wIY_JH6T?t=fsi_f=q8aw`R@o2@yv*Atd^gZ( zM2LnS@~4iK1bkoj^HrAc3Zos*gXA}hu;^DMC2zvS{?+cVVy@|xX{mJt7Zi$&&oUntZ1?>hDgN*`ykk5!u$p=iqc!ff_jS>`{ zOnnMVBX_&va&|5Snby;~fg_k;8&$a3r^ TP0=y}_$Mu{AXf3w(EtAfPlTTZ diff --git a/docs/images/server-operational.png b/docs/images/server-operational.png index cb7e35b67031968b22dcb1ff06237deb10761c0b..b5dddf888839abb9ed41f41b6c879356f98b1282 100644 GIT binary patch literal 28172 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�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 Date: Fri, 10 Aug 2018 14:39:17 +0200 Subject: [PATCH 5/9] Fixed LabeledResources type name (issue #77) --- api/labels/labels_api.go | 2 +- api/labels/labels_api_mocked.go | 8 ++++---- api/types/labels.go | 2 +- testdata/labels_data.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/labels/labels_api.go b/api/labels/labels_api.go index 998b65b..380436f 100644 --- a/api/labels/labels_api.go +++ b/api/labels/labels_api.go @@ -74,7 +74,7 @@ func (lbl *LabelService) CreateLabel(labelVector *map[string]interface{}) (label } // AddLabel assigns a single label from a single labelable resource -func (lbl *LabelService) AddLabel(labelVector *map[string]interface{}, labelID string) (labeledResources []types.LabeledResources, err error) { +func (lbl *LabelService) AddLabel(labelVector *map[string]interface{}, labelID string) (labeledResources []types.LabeledResource, err error) { log.Debug("AddLabel") data, status, err := lbl.concertoService.Post(fmt.Sprintf("/v1/labels/%s/resources", labelID), labelVector) diff --git a/api/labels/labels_api_mocked.go b/api/labels/labels_api_mocked.go index ec0d5e0..bbde988 100644 --- a/api/labels/labels_api_mocked.go +++ b/api/labels/labels_api_mocked.go @@ -253,7 +253,7 @@ func CreateLabelFailJSONMocked(t *testing.T, labelIn *types.Label) *types.Label } // AddLabelMocked test mocked function -func AddLabelMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResources) []types.LabeledResources { +func AddLabelMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResource) []types.LabeledResource { assert := assert.New(t) @@ -281,7 +281,7 @@ func AddLabelMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []ty } // AddLabelFailErrMocked test mocked function -func AddLabelFailErrMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResources) []types.LabeledResources { +func AddLabelFailErrMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResource) []types.LabeledResource { assert := assert.New(t) @@ -310,7 +310,7 @@ func AddLabelFailErrMocked(t *testing.T, labelIn *types.Label, labeledResourcesO } // AddLabelFailStatusMocked test mocked function -func AddLabelFailStatusMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResources) []types.LabeledResources { +func AddLabelFailStatusMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResource) []types.LabeledResource { assert := assert.New(t) @@ -339,7 +339,7 @@ func AddLabelFailStatusMocked(t *testing.T, labelIn *types.Label, labeledResourc } // AddLabelFailJSONMocked test mocked function -func AddLabelFailJSONMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResources) []types.LabeledResources { +func AddLabelFailJSONMocked(t *testing.T, labelIn *types.Label, labeledResourcesOut []types.LabeledResource) []types.LabeledResource { assert := assert.New(t) diff --git a/api/types/labels.go b/api/types/labels.go index b079714..5397a81 100644 --- a/api/types/labels.go +++ b/api/types/labels.go @@ -8,7 +8,7 @@ type Label struct { Value string `json:"value" header:"VALUE" show:"nolist"` } -type LabeledResources struct { +type LabeledResource struct { ID string `json:"id" header:"ID"` ResourceType string `json:"resource_type" header:"RESOURCE_TYPE"` } diff --git a/testdata/labels_data.go b/testdata/labels_data.go index 0b5af93..9597576 100644 --- a/testdata/labels_data.go +++ b/testdata/labels_data.go @@ -47,9 +47,9 @@ func GetLabelWithNamespaceData() *[]types.Label { } // GetLabeledResourcesData loads test data -func GetLabeledResourcesData() *[]types.LabeledResources { +func GetLabeledResourcesData() *[]types.LabeledResource { - testLabeledResources := []types.LabeledResources{ + testLabeledResources := []types.LabeledResource{ { ID: "fakeID0", ResourceType: "server", From 709ede9cf99266966046fb0cd19363371d9f4073 Mon Sep 17 00:00:00 2001 From: Pablo Cantera Date: Fri, 10 Aug 2018 14:45:40 +0200 Subject: [PATCH 6/9] Fixed usage action names for labelable resources (issue #77) --- blueprint/scripts/subcommands.go | 4 ++-- blueprint/templates/subcommands.go | 4 ++-- cloud/servers/subcommands.go | 4 ++-- cloud/ssh_profiles/subcommands.go | 4 ++-- network/firewall_profiles/subcommands.go | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/blueprint/scripts/subcommands.go b/blueprint/scripts/subcommands.go index d5f5aec..93984d6 100644 --- a/blueprint/scripts/subcommands.go +++ b/blueprint/scripts/subcommands.go @@ -96,7 +96,7 @@ func SubCommands() []cli.Command { }, { Name: "add-label", - Usage: "This action assign a single label from a single labelable resource", + Usage: "This action assigns a single label from a single labelable resource", Action: cmd.LabelAdd, Flags: []cli.Flag{ cli.StringFlag{ @@ -117,7 +117,7 @@ func SubCommands() []cli.Command { }, { Name: "remove-label", - Usage: "This action de-assign a single label from a single labelable resource", + Usage: "This action unassigns a single label from a single labelable resource", Action: cmd.LabelRemove, Flags: []cli.Flag{ cli.StringFlag{ diff --git a/blueprint/templates/subcommands.go b/blueprint/templates/subcommands.go index 3c03c6c..557cd3a 100644 --- a/blueprint/templates/subcommands.go +++ b/blueprint/templates/subcommands.go @@ -213,7 +213,7 @@ func SubCommands() []cli.Command { }, { Name: "add-label", - Usage: "This action assign a single label from a single labelable resource", + Usage: "This action assigns a single label from a single labelable resource", Action: cmd.LabelAdd, Flags: []cli.Flag{ cli.StringFlag{ @@ -234,7 +234,7 @@ func SubCommands() []cli.Command { }, { Name: "remove-label", - Usage: "This action de-assign a single label from a single labelable resource", + Usage: "This action unassigns a single label from a single labelable resource", Action: cmd.LabelRemove, Flags: []cli.Flag{ cli.StringFlag{ diff --git a/cloud/servers/subcommands.go b/cloud/servers/subcommands.go index e419ad3..4c9ac7d 100644 --- a/cloud/servers/subcommands.go +++ b/cloud/servers/subcommands.go @@ -174,7 +174,7 @@ func SubCommands() []cli.Command { }, { Name: "add-label", - Usage: "This action assign a single label from a single labelable resource", + Usage: "This action assigns a single label from a single labelable resource", Action: cmd.LabelAdd, Flags: []cli.Flag{ cli.StringFlag{ @@ -195,7 +195,7 @@ func SubCommands() []cli.Command { }, { Name: "remove-label", - Usage: "This action de-assign a single label from a single labelable resource", + Usage: "This action unassigns a single label from a single labelable resource", Action: cmd.LabelRemove, Flags: []cli.Flag{ cli.StringFlag{ diff --git a/cloud/ssh_profiles/subcommands.go b/cloud/ssh_profiles/subcommands.go index 9b8eddb..2122135 100644 --- a/cloud/ssh_profiles/subcommands.go +++ b/cloud/ssh_profiles/subcommands.go @@ -88,7 +88,7 @@ func SubCommands() []cli.Command { }, { Name: "add-label", - Usage: "This action assign a single label from a single labelable resource", + Usage: "This action assigns a single label from a single labelable resource", Action: cmd.LabelAdd, Flags: []cli.Flag{ cli.StringFlag{ @@ -109,7 +109,7 @@ func SubCommands() []cli.Command { }, { Name: "remove-label", - Usage: "This action de-assign a single label from a single labelable resource", + Usage: "This action unassigns a single label from a single labelable resource", Action: cmd.LabelRemove, Flags: []cli.Flag{ cli.StringFlag{ diff --git a/network/firewall_profiles/subcommands.go b/network/firewall_profiles/subcommands.go index e2519a7..4e03f34 100644 --- a/network/firewall_profiles/subcommands.go +++ b/network/firewall_profiles/subcommands.go @@ -88,7 +88,7 @@ func SubCommands() []cli.Command { }, { Name: "add-label", - Usage: "This action assign a single label from a single labelable resource", + Usage: "This action assigns a single label from a single labelable resource", Action: cmd.LabelAdd, Flags: []cli.Flag{ cli.StringFlag{ @@ -109,7 +109,7 @@ func SubCommands() []cli.Command { }, { Name: "remove-label", - Usage: "This action de-assign a single label from a single labelable resource", + Usage: "This action unassigns a single label from a single labelable resource", Action: cmd.LabelRemove, Flags: []cli.Flag{ cli.StringFlag{ From ac7c4cb8fef258fb589a6f1f1f0fbfee63131eea Mon Sep 17 00:00:00 2001 From: Pablo Cantera Date: Tue, 26 Feb 2019 10:42:14 +0100 Subject: [PATCH 7/9] Implemented labelable interface (issue #77) Closes #77 --- api/types/firewall_profiles.go | 15 ++- api/types/labels.go | 47 ++++++++++ api/types/scripts.go | 3 +- api/types/servers.go | 25 +++-- api/types/ssh_profiles.go | 13 ++- api/types/templates.go | 3 +- cmd/firewall_profiles_cmd.go | 31 ++++--- cmd/labels_cmd.go | 90 +++++++----------- cmd/scripts_cmd.go | 31 ++++--- cmd/servers_cmd.go | 39 ++++---- cmd/ssh_profiles_cmd.go | 31 ++++--- cmd/template_cmd.go | 31 ++++--- utils/format/formatter.go | 5 +- utils/format/textformatter.go | 163 +++++++++++++++------------------ 14 files changed, 279 insertions(+), 248 deletions(-) diff --git a/api/types/firewall_profiles.go b/api/types/firewall_profiles.go index 93230b7..8ef416e 100644 --- a/api/types/firewall_profiles.go +++ b/api/types/firewall_profiles.go @@ -1,14 +1,13 @@ package types type FirewallProfile struct { - ID string `json:"id" header:"ID"` - Name string `json:"name,omitempty" header:"NAME"` - Description string `json:"description,omitempty" header:"DESCRIPTION"` - Default bool `json:"default,omitempty" header:"DEFAULT"` - Rules []Rule `json:"rules,omitempty" header:"RULES" show:"nolist"` - ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` - LabelIDs []string `json:"label_ids" header:"LABEL_IDS" show:"nolist,noshow"` - Labels []string `json:"labels" header:"LABELS"` + ID string `json:"id" header:"ID"` + Name string `json:"name,omitempty" header:"NAME"` + Description string `json:"description,omitempty" header:"DESCRIPTION"` + Default bool `json:"default,omitempty" header:"DEFAULT"` + Rules []Rule `json:"rules,omitempty" header:"RULES" show:"nolist"` + ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` + LabelableFields } type Rule struct { diff --git a/api/types/labels.go b/api/types/labels.go index 5397a81..a90a952 100644 --- a/api/types/labels.go +++ b/api/types/labels.go @@ -12,3 +12,50 @@ type LabeledResource struct { ID string `json:"id" header:"ID"` ResourceType string `json:"resource_type" header:"RESOURCE_TYPE"` } + +type LabelableFields struct { + LabelIDs []string `json:"label_ids" header:"LABEL_IDS" show:"nolist,noshow"` + Labels []string `json:"labels" header:"LABELS"` +} + +type Labelable interface { + FilterByLabelIDs(labelIDs []string) bool + AssignLabelIDs(labelIDs []string) + FillInLabelNames(labelNamesByID map[string]string) +} + +func (lf *LabelableFields) FilterByLabelIDs(labelIDs []string) bool { + for _, lid := range labelIDs { + var labelIDFound bool + for _, resourceLabelID := range lf.LabelIDs { + if lid == resourceLabelID { + labelIDFound = true + break + } + } + if !labelIDFound { + return false + } + } + return true +} + +func (lf *LabelableFields) AssignLabelIDs(labelIDs []string) { + for _, lid := range labelIDs { + for _, resourceLabelID := range lf.LabelIDs { + if lid == resourceLabelID { + break + } + } + } +} + +func (lf *LabelableFields) FillInLabelNames(labelNamesByID map[string]string) { + for lID, lName := range labelNamesByID { + for _, resourceLabelID := range lf.LabelIDs { + if lID == resourceLabelID { + lf.Labels = append(lf.Labels, lName) + } + } + } +} diff --git a/api/types/scripts.go b/api/types/scripts.go index 05fbd9d..36d127a 100644 --- a/api/types/scripts.go +++ b/api/types/scripts.go @@ -8,6 +8,5 @@ type Script struct { Code string `json:"code" header:"CODE" show:"nolist"` Parameters []string `json:"parameters" header:"PARAMETERS"` ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` - LabelIDs []string `json:"label_ids" header:"LABEL_IDS" show:"nolist,noshow"` - Labels []string `json:"labels" header:"LABELS"` + LabelableFields } diff --git a/api/types/servers.go b/api/types/servers.go index c008875..dcab057 100644 --- a/api/types/servers.go +++ b/api/types/servers.go @@ -1,19 +1,18 @@ package types type Server struct { - ID string `json:"id" header:"ID"` - Name string `json:"name" header:"NAME"` - Fqdn string `json:"fqdn" header:"FQDN"` - State string `json:"state" header:"STATE"` - PublicIP string `json:"public_ip" header:"PUBLIC_IP"` - TemplateID string `json:"template_id" header:"TEMPLATE_ID"` - ServerPlanID string `json:"server_plan_id" header:"SERVER_PLAN_ID"` - CloudAccountID string `json:"cloud_account_id" header:"CLOUD_ACCOUNT_ID"` - SSHProfileID string `json:"ssh_profile_id" header:"SSH_PROFILE_ID"` - FirewallProfileID string `json:"firewall_profile_id" header:"FIREWALL_PROFILE_ID"` - ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` - LabelIDs []string `json:"label_ids" header:"LABEL_IDS" show:"nolist,noshow"` - Labels []string `json:"labels" header:"LABELS"` + ID string `json:"id" header:"ID"` + Name string `json:"name" header:"NAME"` + Fqdn string `json:"fqdn" header:"FQDN"` + State string `json:"state" header:"STATE"` + PublicIP string `json:"public_ip" header:"PUBLIC_IP"` + TemplateID string `json:"template_id" header:"TEMPLATE_ID"` + ServerPlanID string `json:"server_plan_id" header:"SERVER_PLAN_ID"` + CloudAccountID string `json:"cloud_account_id" header:"CLOUD_ACCOUNT_ID"` + SSHProfileID string `json:"ssh_profile_id" header:"SSH_PROFILE_ID"` + FirewallProfileID string `json:"firewall_profile_id" header:"FIREWALL_PROFILE_ID"` + ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` + LabelableFields } type Dns struct { diff --git a/api/types/ssh_profiles.go b/api/types/ssh_profiles.go index 04a2091..ccd8162 100644 --- a/api/types/ssh_profiles.go +++ b/api/types/ssh_profiles.go @@ -1,11 +1,10 @@ package types type SSHProfile struct { - ID string `json:"id" header:"ID"` - Name string `json:"name" header:"NAME"` - PublicKey string `json:"public_key" header:"PUBLIC_KEY"` - PrivateKey string `json:"private_key" header:"PRIVATE_KEY" show:"nolist"` - ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` - LabelIDs []string `json:"label_ids" header:"LABEL_IDS" show:"nolist,noshow"` - Labels []string `json:"labels" header:"LABELS"` + ID string `json:"id" header:"ID"` + Name string `json:"name" header:"NAME"` + PublicKey string `json:"public_key" header:"PUBLIC_KEY"` + PrivateKey string `json:"private_key" header:"PRIVATE_KEY" show:"nolist"` + ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` + LabelableFields } diff --git a/api/types/templates.go b/api/types/templates.go index 85ff931..dc484cf 100644 --- a/api/types/templates.go +++ b/api/types/templates.go @@ -12,8 +12,7 @@ type Template struct { ServiceList []string `json:"service_list,omitempty" header:"SERVICE LIST" show:"nolist"` ConfigurationAttributes *json.RawMessage `json:"configuration_attributes,omitempty" header:"CONFIGURATION ATTRIBUTES" show:"nolist"` ResourceType string `json:"resource_type" header:"RESOURCE_TYPE" show:"nolist"` - LabelIDs []string `json:"label_ids" header:"LABEL_IDS" show:"nolist,noshow"` - Labels []string `json:"labels" header:"LABELS"` + LabelableFields } // TemplateScript stores a templates' script info diff --git a/cmd/firewall_profiles_cmd.go b/cmd/firewall_profiles_cmd.go index 569bbea..4842732 100644 --- a/cmd/firewall_profiles_cmd.go +++ b/cmd/firewall_profiles_cmd.go @@ -39,18 +39,25 @@ func FirewallProfileList(c *cli.Context) error { formatter.PrintFatal("Couldn't receive firewallProfile data", err) } - filteredResources, err := LabelFiltering(c, firewallProfiles) - if err != nil { - formatter.PrintFatal("Couldn't list firewall profiles filtered by labels", err) - } - if filteredResources != nil { - firewallProfiles = nil - for _, v := range *filteredResources { - firewallProfiles = append(firewallProfiles, v.(types.FirewallProfile)) + labelables := make([]*types.LabelableFields, 0, len(firewallProfiles)) + for i := range firewallProfiles { + labelables = append(labelables, &firewallProfiles[i].LabelableFields) + } + + filteredLabelables := LabelFiltering(c, labelables) + + tmp := firewallProfiles + firewallProfiles = nil + if len(filteredLabelables) > 0 { + for _, labelable := range filteredLabelables { + for i := range tmp { + if &tmp[i].LabelableFields == labelable { + firewallProfiles = append(firewallProfiles, tmp[i]) + } + } } } - LabelAssignNamesForIDs(c, firewallProfiles) if err = formatter.PrintList(firewallProfiles); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -68,7 +75,7 @@ func FirewallProfileShow(c *cli.Context) error { formatter.PrintFatal("Couldn't receive firewallProfile data", err) } - LabelAssignNamesForIDs(c, firewallProfile) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&firewallProfile.LabelableFields}) if err = formatter.PrintItem(*firewallProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -103,7 +110,7 @@ func FirewallProfileCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create firewallProfile", err) } - LabelAssignNamesForIDs(c, firewallProfile) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&firewallProfile.LabelableFields}) if err = formatter.PrintItem(*firewallProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -125,7 +132,7 @@ func FirewallProfileUpdate(c *cli.Context) error { formatter.PrintFatal("Couldn't update firewallProfile", err) } - LabelAssignNamesForIDs(c, firewallProfile) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&firewallProfile.LabelableFields}) if err = formatter.PrintItem(*firewallProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/labels_cmd.go b/cmd/labels_cmd.go index 5915e14..245618e 100644 --- a/cmd/labels_cmd.go +++ b/cmd/labels_cmd.go @@ -2,12 +2,11 @@ package cmd import ( "fmt" - "reflect" "regexp" "strings" - "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/labels" + "github.com/ingrammicro/concerto/api/types" "github.com/ingrammicro/concerto/utils" "github.com/ingrammicro/concerto/utils/format" ) @@ -65,76 +64,51 @@ func LabelCreate(c *cli.Context) error { return nil } -// LabelFiltering subcommand function receives an interface representing a collection of labelable resources (Server, Template, ...) +// LabelFiltering subcommand function receives a collection of references to labelable objects // Evaluates the matching of assigned labels with the labels requested for filtering. -func LabelFiltering(c *cli.Context, items interface{}) (*[]interface{}, error) { +func LabelFiltering(c *cli.Context, inItems []*types.LabelableFields) []*types.LabelableFields { debugCmdFuncInfo(c) - if c.String("labels") != "" { - its := reflect.ValueOf(items) - if its.Type().Kind() != reflect.Slice { - return nil, fmt.Errorf("Cannot process label filtering. Slice expected") - } + labelsMapNameToID, labelsMapIDToName := LabelLoadsMapping(c) - // evaluates labels + var outItems []*types.LabelableFields + if c.String("labels") != "" { _, formatter := WireUpLabel(c) labelNamesIn := LabelsUnifyInputNames(c.String("labels"), formatter) - // Load Labels mapping ID <-> NAME - _, labelsMapIDToName := LabelLoadsMapping(c) - - var filteredResources []interface{} - var tmpLabelNames []string - // per resource (Server, Template, ...) - for i := 0; i < its.Len(); i++ { - tmpLabelNames = nil - labelIDs := reflect.ValueOf(its.Index(i).FieldByName("LabelIDs").Interface()) - if len := labelIDs.Len(); len > 0 { - for j := 0; j < len; j++ { - tmpLabelNames = append(tmpLabelNames, labelsMapIDToName[labelIDs.Index(j).String()]) - } - } - // checks whether received labels match for resources labels - if utils.Subset(labelNamesIn, tmpLabelNames) { - filteredResources = append(filteredResources, its.Index(i).Interface()) + var labelIDsIn []string + for _, name := range labelNamesIn { + labelIDsIn = append(labelIDsIn, labelsMapNameToID[name]) + } + + for i := 0; i < len(inItems); i++ { + if inItems[i].FilterByLabelIDs(labelIDsIn) { + // added filtered + outItems = append(outItems, inItems[i]) } } - return &filteredResources, nil + } else { + // all included + outItems = inItems + } + + // Assigns the Labels names + for i := 0; i < len(outItems); i++ { + outItems[i].FillInLabelNames(labelsMapIDToName) } - return nil, nil + + return outItems } -// LabelAssignNamesForIDs subcommand function receives an interface representing labelable resources (Server, Template, ...) +// LabelAssignNamesForIDs subcommand function receives a collection of references to labelables objects // Resolves the Labels names associated to a each resource from given Labels ids, loading object with respective labels names -func LabelAssignNamesForIDs(c *cli.Context, items interface{}) { +func LabelAssignNamesForIDs(c *cli.Context, items []*types.LabelableFields) { debugCmdFuncInfo(c) - var tmpLabelNames []string - // Load Labels mapping ID <-> NAME _, labelsMapIDToName := LabelLoadsMapping(c) - - its := reflect.ValueOf(items) - if its.Type().Kind() == reflect.Slice { // resources collection - // per resource (Server, Template, ...) - for i := 0; i < its.Len(); i++ { - tmpLabelNames = nil - labelIDs := reflect.ValueOf(its.Index(i).FieldByName("LabelIDs").Interface()) - if len := labelIDs.Len(); len > 0 { - for j := 0; j < len; j++ { - tmpLabelNames = append(tmpLabelNames, labelsMapIDToName[labelIDs.Index(j).String()]) - } - } - its.Index(i).FieldByName("Labels").Set(reflect.ValueOf(tmpLabelNames)) - } - } else if its.Type().Kind() == reflect.Ptr { // resource - labelIDs := reflect.Indirect(its).FieldByName("LabelIDs") - if len := labelIDs.Len(); len > 0 { - for j := 0; j < len; j++ { - tmpLabelNames = append(tmpLabelNames, labelsMapIDToName[labelIDs.Index(j).String()]) - } - } - reflect.Indirect(its).FieldByName("Labels").Set(reflect.ValueOf(tmpLabelNames)) + for i := 0; i < len(items); i++ { + items[i].FillInLabelNames(labelsMapIDToName) } } @@ -171,9 +145,9 @@ func LabelsUnifyInputNames(labelsNames string, formatter format.Formatter) []str return labelNamesIn } -// LabelResolution subcommand function retrieves a labels map(Name<->ID) based on label names received to be procesed. +// LabelResolution subcommand function retrieves a labels map(Name<->ID) based on label names received to be processed. // The function evaluates the received labels names (comma separated string); with them, solves the assigned IDs for the given labels names. -// If the label name is not avaiable in IMCO yet, it is created. +// If the label name is not available in IMCO yet, it is created. func LabelResolution(c *cli.Context, labelsNames string) []string { debugCmdFuncInfo(c) @@ -181,7 +155,7 @@ func LabelResolution(c *cli.Context, labelsNames string) []string { labelNamesIn := LabelsUnifyInputNames(labelsNames, formatter) labelsMapNameToID, _ := LabelLoadsMapping(c) - // Obtain output mapped labels Name<->ID; currenlty in IMCO platform as well as if creation is required + // Obtain output mapped labels Name<->ID; currently in IMCO platform as well as if creation is required labelsOutMap := make(map[string]string) for _, name := range labelNamesIn { // check if the label already exists in IMCO, creates it if it does not exist diff --git a/cmd/scripts_cmd.go b/cmd/scripts_cmd.go index 83f610a..a194982 100644 --- a/cmd/scripts_cmd.go +++ b/cmd/scripts_cmd.go @@ -41,18 +41,25 @@ func ScriptsList(c *cli.Context) error { formatter.PrintFatal("Couldn't receive script data", err) } - filteredResources, err := LabelFiltering(c, scripts) - if err != nil { - formatter.PrintFatal("Couldn't list scripts filtered by labels", err) - } - if filteredResources != nil { - scripts = nil - for _, v := range *filteredResources { - scripts = append(scripts, v.(types.Script)) + labelables := make([]*types.LabelableFields, 0, len(scripts)) + for i := range scripts { + labelables = append(labelables, &scripts[i].LabelableFields) + } + + filteredLabelables := LabelFiltering(c, labelables) + + tmp := scripts + scripts = nil + if len(filteredLabelables) > 0 { + for _, labelable := range filteredLabelables { + for i := range tmp { + if &tmp[i].LabelableFields == labelable { + scripts = append(scripts, tmp[i]) + } + } } } - LabelAssignNamesForIDs(c, scripts) if err = formatter.PrintList(scripts); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -70,7 +77,7 @@ func ScriptShow(c *cli.Context) error { formatter.PrintFatal("Couldn't receive script data", err) } - LabelAssignNamesForIDs(c, script) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&script.LabelableFields}) if err = formatter.PrintItem(*script); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -101,7 +108,7 @@ func ScriptCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create script", err) } - LabelAssignNamesForIDs(c, script) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&script.LabelableFields}) if err = formatter.PrintItem(*script); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -119,7 +126,7 @@ func ScriptUpdate(c *cli.Context) error { formatter.PrintFatal("Couldn't update script", err) } - LabelAssignNamesForIDs(c, script) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&script.LabelableFields}) if err = formatter.PrintItem(*script); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/servers_cmd.go b/cmd/servers_cmd.go index 8bd30d2..467060b 100644 --- a/cmd/servers_cmd.go +++ b/cmd/servers_cmd.go @@ -39,18 +39,25 @@ func ServerList(c *cli.Context) error { formatter.PrintFatal("Couldn't receive server data", err) } - filteredResources, err := LabelFiltering(c, servers) - if err != nil { - formatter.PrintFatal("Couldn't list servers filtered by labels", err) - } - if filteredResources != nil { - servers = nil - for _, v := range *filteredResources { - servers = append(servers, v.(types.Server)) + labelables := make([]*types.LabelableFields, 0, len(servers)) + for i := range servers { + labelables = append(labelables, &servers[i].LabelableFields) + } + + filteredLabelables := LabelFiltering(c, labelables) + + tmp := servers + servers = nil + if len(filteredLabelables) > 0 { + for _, labelable := range filteredLabelables { + for i := range tmp { + if &tmp[i].LabelableFields == labelable { + servers = append(servers, tmp[i]) + } + } } } - LabelAssignNamesForIDs(c, servers) if err = formatter.PrintList(servers); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -68,7 +75,7 @@ func ServerShow(c *cli.Context) error { formatter.PrintFatal("Couldn't receive server data", err) } - LabelAssignNamesForIDs(c, server) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -100,7 +107,7 @@ func ServerCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create server", err) } - LabelAssignNamesForIDs(c, server) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -118,7 +125,7 @@ func ServerUpdate(c *cli.Context) error { formatter.PrintFatal("Couldn't update server", err) } - LabelAssignNamesForIDs(c, server) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -136,7 +143,7 @@ func ServerBoot(c *cli.Context) error { formatter.PrintFatal("Couldn't boot server", err) } - LabelAssignNamesForIDs(c, server) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -154,7 +161,7 @@ func ServerReboot(c *cli.Context) error { formatter.PrintFatal("Couldn't reboot server", err) } - LabelAssignNamesForIDs(c, server) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -172,7 +179,7 @@ func ServerShutdown(c *cli.Context) error { formatter.PrintFatal("Couldn't shutdown server", err) } - LabelAssignNamesForIDs(c, server) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -190,7 +197,7 @@ func ServerOverride(c *cli.Context) error { formatter.PrintFatal("Couldn't override server", err) } - LabelAssignNamesForIDs(c, server) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/ssh_profiles_cmd.go b/cmd/ssh_profiles_cmd.go index 8d01bba..7853ca9 100644 --- a/cmd/ssh_profiles_cmd.go +++ b/cmd/ssh_profiles_cmd.go @@ -39,18 +39,25 @@ func SSHProfileList(c *cli.Context) error { formatter.PrintFatal("Couldn't receive sshProfile data", err) } - filteredResources, err := LabelFiltering(c, sshProfiles) - if err != nil { - formatter.PrintFatal("Couldn't list SSH profiles filtered by labels", err) - } - if filteredResources != nil { - sshProfiles = nil - for _, v := range *filteredResources { - sshProfiles = append(sshProfiles, v.(types.SSHProfile)) + labelables := make([]*types.LabelableFields, 0, len(sshProfiles)) + for i := range sshProfiles { + labelables = append(labelables, &sshProfiles[i].LabelableFields) + } + + filteredLabelables := LabelFiltering(c, labelables) + + tmp := sshProfiles + sshProfiles = nil + if len(filteredLabelables) > 0 { + for _, labelable := range filteredLabelables { + for i := range tmp { + if &tmp[i].LabelableFields == labelable { + sshProfiles = append(sshProfiles, tmp[i]) + } + } } } - LabelAssignNamesForIDs(c, sshProfiles) if err = formatter.PrintList(sshProfiles); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -68,7 +75,7 @@ func SSHProfileShow(c *cli.Context) error { formatter.PrintFatal("Couldn't receive sshProfile data", err) } - LabelAssignNamesForIDs(c, sshProfile) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&sshProfile.LabelableFields}) if err = formatter.PrintItem(*sshProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -98,7 +105,7 @@ func SSHProfileCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create sshProfile", err) } - LabelAssignNamesForIDs(c, sshProfile) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&sshProfile.LabelableFields}) if err = formatter.PrintItem(*sshProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -116,7 +123,7 @@ func SSHProfileUpdate(c *cli.Context) error { formatter.PrintFatal("Couldn't update sshProfile", err) } - LabelAssignNamesForIDs(c, sshProfile) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&sshProfile.LabelableFields}) if err = formatter.PrintItem(*sshProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/template_cmd.go b/cmd/template_cmd.go index ab181e5..4e97873 100644 --- a/cmd/template_cmd.go +++ b/cmd/template_cmd.go @@ -39,18 +39,25 @@ func TemplateList(c *cli.Context) error { formatter.PrintFatal("Couldn't receive template data", err) } - filteredResources, err := LabelFiltering(c, templates) - if err != nil { - formatter.PrintFatal("Couldn't list templates filtered by labels", err) - } - if filteredResources != nil { - templates = nil - for _, v := range *filteredResources { - templates = append(templates, v.(types.Template)) + labelables := make([]*types.LabelableFields, 0, len(templates)) + for i := range templates { + labelables = append(labelables, &templates[i].LabelableFields) + } + + filteredLabelables := LabelFiltering(c, labelables) + + tmp := templates + templates = nil + if len(filteredLabelables) > 0 { + for _, labelable := range filteredLabelables { + for i := range tmp { + if &tmp[i].LabelableFields == labelable { + templates = append(templates, tmp[i]) + } + } } } - LabelAssignNamesForIDs(c, templates) if err = formatter.PrintList(templates); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -68,7 +75,7 @@ func TemplateShow(c *cli.Context) error { formatter.PrintFatal("Couldn't receive template data", err) } - LabelAssignNamesForIDs(c, template) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&template.LabelableFields}) if err = formatter.PrintItem(*template); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -104,7 +111,7 @@ func TemplateCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create template", err) } - LabelAssignNamesForIDs(c, template) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&template.LabelableFields}) if err = formatter.PrintItem(*template); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -129,7 +136,7 @@ func TemplateUpdate(c *cli.Context) error { formatter.PrintFatal("Couldn't update template", err) } - LabelAssignNamesForIDs(c, template) + LabelAssignNamesForIDs(c, []*types.LabelableFields{&template.LabelableFields}) if err = formatter.PrintItem(*template); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/utils/format/formatter.go b/utils/format/formatter.go index 0ebbb6f..869f4e8 100644 --- a/utils/format/formatter.go +++ b/utils/format/formatter.go @@ -16,7 +16,6 @@ var osExit = os.Exit type Formatter interface { PrintItem(item interface{}) error PrintList(items interface{}) error - //PrintList(items [][]string, headers []string) error PrintError(context string, err error) PrintFatal(context string, err error) } @@ -24,8 +23,8 @@ type Formatter interface { var formatter Formatter // InitializeFormatter creates a singleton Formatter -func InitializeFormatter(ftype string, out io.Writer) { - if ftype == "json" { +func InitializeFormatter(formatterType string, out io.Writer) { + if formatterType == "json" { formatter = NewJSONFormatter(out) } else { formatter = NewTextFormatter(out) diff --git a/utils/format/textformatter.go b/utils/format/textformatter.go index 67f5680..3acbe43 100644 --- a/utils/format/textformatter.go +++ b/utils/format/textformatter.go @@ -2,6 +2,7 @@ package format import ( "fmt" + "github.com/ingrammicro/concerto/utils" "io" "reflect" "strings" @@ -10,8 +11,6 @@ import ( log "github.com/Sirupsen/logrus" ) -const minifySeconds string = "minifySeconds" - // TextFormatter prints items and lists type TextFormatter struct { output io.Writer @@ -26,42 +25,85 @@ func NewTextFormatter(out io.Writer) *TextFormatter { } } -// PrintItem prints an item -func (f *TextFormatter) PrintItem(item interface{}) error { - log.Debug("PrintItem") +func (f *TextFormatter) printItemAux(w *tabwriter.Writer, item interface{}) error { + log.Debug("printItemAux") it := reflect.ValueOf(item) - nf := it.NumField() - - w := tabwriter.NewWriter(f.output, 15, 1, 3, ' ', 0) - for i := 0; i < nf; i++ { - // hide fields - bShow := true + for i := 0; i < it.NumField(); i++ { showTags := strings.Split(it.Type().Field(i).Tag.Get("show"), ",") - for _, showTag := range showTags { - if showTag == "noshow" { - bShow = false - } - } - - if bShow { - // TODO not the best way to use reflection. Check this later + if !utils.Contains(showTags, "noshow") { switch it.Field(i).Type().String() { case "json.RawMessage": - fmt.Fprintf(w, "%s:\t%s\n", it.Type().Field(i).Tag.Get("header"), it.Field(i).Interface()) + fmt.Fprintln(w, fmt.Sprintf("%s:\t%s", it.Type().Field(i).Tag.Get("header"), it.Field(i).Interface())) case "*json.RawMessage": - fmt.Fprintf(w, "%s:\t%s\n", it.Type().Field(i).Tag.Get("header"), it.Field(i).Elem()) + fmt.Fprintln(w, fmt.Sprintf("%s:\t%s", it.Type().Field(i).Tag.Get("header"), it.Field(i).Elem())) default: - fmt.Fprintf(w, "%s:\t%+v\n", it.Type().Field(i).Tag.Get("header"), it.Field(i).Interface()) + if it.Field(i).Kind() == reflect.Struct { + f.printItemAux(w, it.Field(i).Interface()) + } else { + fmt.Fprintln(w, fmt.Sprintf("%s:\t%+v", it.Type().Field(i).Tag.Get("header"), it.Field(i).Interface())) + } } } } - fmt.Fprintln(w) + return nil +} + +// PrintItem prints item +func (f *TextFormatter) PrintItem(item interface{}) error { + log.Debug("PrintItem") + + w := tabwriter.NewWriter(f.output, 15, 1, 3, ' ', 0) + f.printItemAux(w, item) w.Flush() return nil } +func (f *TextFormatter) printListHeadersAux(w *tabwriter.Writer, t reflect.Type) { + log.Debug("printListHeadersAux") + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + + if field.Type.Kind() == reflect.Struct { + f.printListHeadersAux(w, field.Type) + } + + showTags := strings.Split(field.Tag.Get("show"), ",") + if !utils.Contains(showTags, "nolist") { + fmt.Fprint(w, fmt.Sprintf("%+v\t", field.Tag.Get("header"))) + } + } +} + +func (f *TextFormatter) printListBodyAux(w *tabwriter.Writer, t reflect.Value) { + log.Debug("printListBodyAux") + + for i := 0; i < t.NumField(); i++ { + showTags := strings.Split(t.Type().Field(i).Tag.Get("show"), ",") + if !utils.Contains(showTags, "nolist") { + field := t.Field(i) + switch field.Type().String() { + case "json.RawMessage": + fmt.Fprint(w, fmt.Sprintf("%s\t", field.Interface())) + case "*json.RawMessage": + if field.IsNil() { + fmt.Fprint(w, fmt.Sprintf(" \t")) + } else { + fmt.Fprint(w, fmt.Sprintf("%s\t", field.Elem())) + } + default: + if field.Kind() == reflect.Struct { + f.printListBodyAux(w, field) + } else { + fmt.Fprint(w, fmt.Sprintf("%+v\t", field.Interface())) + } + } + } + } +} + // PrintList prints item list func (f *TextFormatter) PrintList(items interface{}) error { log.Debug("PrintList") @@ -70,82 +112,21 @@ func (f *TextFormatter) PrintList(items interface{}) error { its := reflect.ValueOf(items) t := its.Type().Kind() if t != reflect.Slice { - return fmt.Errorf("Couldn't print list. Expected slice, but received %s", t.String()) + return fmt.Errorf("couldn't print list. Expected slice, but received %s", t.String()) } w := tabwriter.NewWriter(f.output, 15, 1, 3, ' ', 0) - header := reflect.TypeOf(items).Elem() - nf := header.NumField() - - // avoid printing elements with 'show:nolist' attribute - // special format tags - avoid := make([]bool, nf) - format := make([]string, nf) - for i := 0; i < nf; i++ { - avoid[i] = false - showTags := strings.Split(header.Field(i).Tag.Get("show"), ",") - for _, showTag := range showTags { - if showTag == "nolist" { - avoid[i] = true - } - if showTag == minifySeconds { - format[i] = minifySeconds - } - } - } - - // print header - for i := 0; i < nf; i++ { - if !avoid[i] { - fmt.Fprintf(w, "%+v\t", header.Field(i).Tag.Get("header")) - } - } + // Headers + f.printListHeadersAux(w, reflect.TypeOf(items).Elem()) fmt.Fprintln(w) - // print contents - for i := 0; i < its.Len(); i++ { - it := its.Index(i) - nf := it.NumField() - for i := 0; i < nf; i++ { - if !avoid[i] { - - if format[i] == minifySeconds { - - remainingSeconds := int(it.Field(i).Float()) - s := remainingSeconds % 60 - remainingSeconds = (remainingSeconds - s) - m := int(remainingSeconds/60) % 60 - remainingSeconds = (remainingSeconds - m*60) - h := (remainingSeconds / 3600) % 24 - remainingSeconds = (remainingSeconds - h*3600) - d := int(remainingSeconds / 86400) - - if d > 0 { - fmt.Fprintf(w, "%dd%dh%dm\t", d, h, m) - } else { - fmt.Fprintf(w, "%dh%dm%ds\t", h, m, s) - } - - } else { - - switch it.Field(i).Type().String() { - case "json.RawMessage": - fmt.Fprintf(w, "%s\t", it.Field(i).Interface()) - case "*json.RawMessage": - if it.Field(i).IsNil() { - fmt.Fprintf(w, " \t") - } else { - fmt.Fprintf(w, "%s\t", it.Field(i).Elem()) - } - default: - fmt.Fprintf(w, "%+v\t", it.Field(i).Interface()) - } - } - } - } + // Body + for pos := 0; pos < its.Len(); pos++ { + f.printListBodyAux(w, its.Index(pos)) fmt.Fprintln(w) } + w.Flush() return nil From 5ff3a39e13a5f079a3363f47624d9d3640f0317d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Ban=CC=83os=20Lo=CC=81pez?= Date: Tue, 19 Mar 2019 11:12:22 +0100 Subject: [PATCH 8/9] Refactor to make better use of Labelable interface (issue #77) --- cmd/firewall_profiles_cmd.go | 42 ++++++++++++++-------------- cmd/labels_cmd.go | 53 ++++++++++++++---------------------- cmd/scripts_cmd.go | 36 +++++++++++++----------- cmd/servers_cmd.go | 50 ++++++++++++++++++++-------------- cmd/ssh_profiles_cmd.go | 40 ++++++++++++++------------- cmd/template_cmd.go | 38 ++++++++++++++------------ 6 files changed, 133 insertions(+), 126 deletions(-) diff --git a/cmd/firewall_profiles_cmd.go b/cmd/firewall_profiles_cmd.go index 4842732..a3595b1 100644 --- a/cmd/firewall_profiles_cmd.go +++ b/cmd/firewall_profiles_cmd.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/network" "github.com/ingrammicro/concerto/api/types" @@ -39,25 +41,22 @@ func FirewallProfileList(c *cli.Context) error { formatter.PrintFatal("Couldn't receive firewallProfile data", err) } - labelables := make([]*types.LabelableFields, 0, len(firewallProfiles)) - for i := range firewallProfiles { - labelables = append(labelables, &firewallProfiles[i].LabelableFields) + labelables := make([]types.Labelable, len(firewallProfiles)) + for i, fwp := range firewallProfiles { + labelables[i] = types.Labelable(&fwp) } - - filteredLabelables := LabelFiltering(c, labelables) - - tmp := firewallProfiles - firewallProfiles = nil - if len(filteredLabelables) > 0 { - for _, labelable := range filteredLabelables { - for i := range tmp { - if &tmp[i].LabelableFields == labelable { - firewallProfiles = append(firewallProfiles, tmp[i]) - } - } + labelIDsByName, labelNamesByID := LabelLoadsMapping(c) + filteredLabelables := LabelFiltering(c, labelables, labelIDsByName) + LabelAssignNamesForIDs(c, filteredLabelables, labelNamesByID) + firewallProfiles = make([]types.FirewallProfile, len(filteredLabelables)) + for i, labelable := range labelables { + fw, ok := labelable.(*types.FirewallProfile) + if !ok { + formatter.PrintFatal("Label filtering returned unexpected result", + fmt.Errorf("expected labelable to be a *types.FirewallProfile, got a %T", labelable)) } + firewallProfiles[i] = *fw } - if err = formatter.PrintList(firewallProfiles); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -74,8 +73,8 @@ func FirewallProfileShow(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive firewallProfile data", err) } - - LabelAssignNamesForIDs(c, []*types.LabelableFields{&firewallProfile.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + firewallProfile.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*firewallProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -109,8 +108,8 @@ func FirewallProfileCreate(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't create firewallProfile", err) } - - LabelAssignNamesForIDs(c, []*types.LabelableFields{&firewallProfile.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + firewallProfile.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*firewallProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -132,7 +131,8 @@ func FirewallProfileUpdate(c *cli.Context) error { formatter.PrintFatal("Couldn't update firewallProfile", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&firewallProfile.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + firewallProfile.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*firewallProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/labels_cmd.go b/cmd/labels_cmd.go index 245618e..e97c247 100644 --- a/cmd/labels_cmd.go +++ b/cmd/labels_cmd.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "strings" + "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/labels" "github.com/ingrammicro/concerto/api/types" @@ -66,49 +67,37 @@ func LabelCreate(c *cli.Context) error { // LabelFiltering subcommand function receives a collection of references to labelable objects // Evaluates the matching of assigned labels with the labels requested for filtering. -func LabelFiltering(c *cli.Context, inItems []*types.LabelableFields) []*types.LabelableFields { +func LabelFiltering(c *cli.Context, items []types.Labelable, labelIDsByName map[string]string) []types.Labelable { debugCmdFuncInfo(c) - labelsMapNameToID, labelsMapIDToName := LabelLoadsMapping(c) - - var outItems []*types.LabelableFields if c.String("labels") != "" { _, formatter := WireUpLabel(c) labelNamesIn := LabelsUnifyInputNames(c.String("labels"), formatter) - - var labelIDsIn []string + var filteringLabelIDs []string for _, name := range labelNamesIn { - labelIDsIn = append(labelIDsIn, labelsMapNameToID[name]) + id := labelIDsByName[name] + if id != "" { + filteringLabelIDs = append(filteringLabelIDs, id) + } } - - for i := 0; i < len(inItems); i++ { - if inItems[i].FilterByLabelIDs(labelIDsIn) { - // added filtered - outItems = append(outItems, inItems[i]) + var result []types.Labelable + for _, item := range items { + if item.FilterByLabelIDs(filteringLabelIDs) { + result = append(result, item) } } - } else { - // all included - outItems = inItems - } - - // Assigns the Labels names - for i := 0; i < len(outItems); i++ { - outItems[i].FillInLabelNames(labelsMapIDToName) + return result } - return outItems + return items } // LabelAssignNamesForIDs subcommand function receives a collection of references to labelables objects // Resolves the Labels names associated to a each resource from given Labels ids, loading object with respective labels names -func LabelAssignNamesForIDs(c *cli.Context, items []*types.LabelableFields) { +func LabelAssignNamesForIDs(c *cli.Context, items []types.Labelable, labelNamesByID map[string]string) { debugCmdFuncInfo(c) - - // Load Labels mapping ID <-> NAME - _, labelsMapIDToName := LabelLoadsMapping(c) - for i := 0; i < len(items); i++ { - items[i].FillInLabelNames(labelsMapIDToName) + for _, labelable := range items { + labelable.FillInLabelNames(labelNamesByID) } } @@ -122,14 +111,14 @@ func LabelLoadsMapping(c *cli.Context) (map[string]string, map[string]string) { formatter.PrintFatal("Couldn't receive labels data", err) } - labelsMapNameToID := make(map[string]string) - labelsMapIDToName := make(map[string]string) + labelIDsByName := make(map[string]string) + labelNamesByID := make(map[string]string) for _, label := range labels { - labelsMapNameToID[label.Name] = label.ID - labelsMapIDToName[label.ID] = label.Name + labelIDsByName[label.Name] = label.ID + labelNamesByID[label.ID] = label.Name } - return labelsMapNameToID, labelsMapIDToName + return labelIDsByName, labelNamesByID } // LabelsUnifyInputNames subcommand function evaluates the received labels names (comma separated string). diff --git a/cmd/scripts_cmd.go b/cmd/scripts_cmd.go index a194982..2711f3d 100644 --- a/cmd/scripts_cmd.go +++ b/cmd/scripts_cmd.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "strings" "github.com/codegangsta/cli" @@ -41,23 +42,23 @@ func ScriptsList(c *cli.Context) error { formatter.PrintFatal("Couldn't receive script data", err) } - labelables := make([]*types.LabelableFields, 0, len(scripts)) - for i := range scripts { - labelables = append(labelables, &scripts[i].LabelableFields) + labelables := make([]types.Labelable, len(scripts)) + for i, sc := range scripts { + labelables[i] = types.Labelable(&sc) } - filteredLabelables := LabelFiltering(c, labelables) + labelIDsByName, labelNamesByID := LabelLoadsMapping(c) + filteredLabelables := LabelFiltering(c, labelables, labelIDsByName) + LabelAssignNamesForIDs(c, filteredLabelables, labelNamesByID) - tmp := scripts - scripts = nil - if len(filteredLabelables) > 0 { - for _, labelable := range filteredLabelables { - for i := range tmp { - if &tmp[i].LabelableFields == labelable { - scripts = append(scripts, tmp[i]) - } - } + scripts = make([]types.Script, len(filteredLabelables)) + for i, labelable := range labelables { + fw, ok := labelable.(*types.Script) + if !ok { + formatter.PrintFatal("Label filtering returned unexpected result", + fmt.Errorf("expected labelable to be a *types.Script, got a %T", labelable)) } + scripts[i] = *fw } if err = formatter.PrintList(scripts); err != nil { @@ -77,7 +78,8 @@ func ScriptShow(c *cli.Context) error { formatter.PrintFatal("Couldn't receive script data", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&script.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + script.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*script); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -108,7 +110,8 @@ func ScriptCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create script", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&script.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + script.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*script); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -126,7 +129,8 @@ func ScriptUpdate(c *cli.Context) error { formatter.PrintFatal("Couldn't update script", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&script.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + script.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*script); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/servers_cmd.go b/cmd/servers_cmd.go index 467060b..63a250f 100644 --- a/cmd/servers_cmd.go +++ b/cmd/servers_cmd.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/cloud" "github.com/ingrammicro/concerto/api/types" @@ -39,25 +41,24 @@ func ServerList(c *cli.Context) error { formatter.PrintFatal("Couldn't receive server data", err) } - labelables := make([]*types.LabelableFields, 0, len(servers)) - for i := range servers { - labelables = append(labelables, &servers[i].LabelableFields) + labelables := make([]types.Labelable, len(servers)) + for i, server := range servers { + labelables[i] = types.Labelable(&server) } - filteredLabelables := LabelFiltering(c, labelables) + labelIDsByName, labelNamesByID := LabelLoadsMapping(c) + filteredLabelables := LabelFiltering(c, labelables, labelIDsByName) + LabelAssignNamesForIDs(c, filteredLabelables, labelNamesByID) - tmp := servers - servers = nil - if len(filteredLabelables) > 0 { - for _, labelable := range filteredLabelables { - for i := range tmp { - if &tmp[i].LabelableFields == labelable { - servers = append(servers, tmp[i]) - } - } + servers = make([]types.Server, len(filteredLabelables)) + for i, labelable := range labelables { + s, ok := labelable.(*types.Server) + if !ok { + formatter.PrintFatal("Label filtering returned unexpected result", + fmt.Errorf("expected labelable to be a *types.Server, got a %T", labelable)) } + servers[i] = *s } - if err = formatter.PrintList(servers); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -75,7 +76,8 @@ func ServerShow(c *cli.Context) error { formatter.PrintFatal("Couldn't receive server data", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + server.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -107,7 +109,8 @@ func ServerCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create server", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + server.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -125,7 +128,8 @@ func ServerUpdate(c *cli.Context) error { formatter.PrintFatal("Couldn't update server", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + server.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -143,7 +147,8 @@ func ServerBoot(c *cli.Context) error { formatter.PrintFatal("Couldn't boot server", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + server.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -161,7 +166,8 @@ func ServerReboot(c *cli.Context) error { formatter.PrintFatal("Couldn't reboot server", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + server.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -179,7 +185,8 @@ func ServerShutdown(c *cli.Context) error { formatter.PrintFatal("Couldn't shutdown server", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + server.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -197,7 +204,8 @@ func ServerOverride(c *cli.Context) error { formatter.PrintFatal("Couldn't override server", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&server.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + server.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/ssh_profiles_cmd.go b/cmd/ssh_profiles_cmd.go index 7853ca9..8aa3e36 100644 --- a/cmd/ssh_profiles_cmd.go +++ b/cmd/ssh_profiles_cmd.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/cloud" "github.com/ingrammicro/concerto/api/types" @@ -39,23 +41,21 @@ func SSHProfileList(c *cli.Context) error { formatter.PrintFatal("Couldn't receive sshProfile data", err) } - labelables := make([]*types.LabelableFields, 0, len(sshProfiles)) - for i := range sshProfiles { - labelables = append(labelables, &sshProfiles[i].LabelableFields) + labelables := make([]types.Labelable, len(sshProfiles)) + for i, sshP := range sshProfiles { + labelables[i] = types.Labelable(&sshP) } - - filteredLabelables := LabelFiltering(c, labelables) - - tmp := sshProfiles - sshProfiles = nil - if len(filteredLabelables) > 0 { - for _, labelable := range filteredLabelables { - for i := range tmp { - if &tmp[i].LabelableFields == labelable { - sshProfiles = append(sshProfiles, tmp[i]) - } - } + labelIDsByName, labelNamesByID := LabelLoadsMapping(c) + filteredLabelables := LabelFiltering(c, labelables, labelIDsByName) + LabelAssignNamesForIDs(c, filteredLabelables, labelNamesByID) + sshProfiles = make([]types.SSHProfile, len(filteredLabelables)) + for i, labelable := range labelables { + fw, ok := labelable.(*types.SSHProfile) + if !ok { + formatter.PrintFatal("Label filtering returned unexpected result", + fmt.Errorf("expected labelable to be a *types.SSHProfile, got a %T", labelable)) } + sshProfiles[i] = *fw } if err = formatter.PrintList(sshProfiles); err != nil { @@ -74,8 +74,8 @@ func SSHProfileShow(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't receive sshProfile data", err) } - - LabelAssignNamesForIDs(c, []*types.LabelableFields{&sshProfile.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + sshProfile.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*sshProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -105,7 +105,8 @@ func SSHProfileCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create sshProfile", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&sshProfile.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + sshProfile.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*sshProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -123,7 +124,8 @@ func SSHProfileUpdate(c *cli.Context) error { formatter.PrintFatal("Couldn't update sshProfile", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&sshProfile.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + sshProfile.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*sshProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } diff --git a/cmd/template_cmd.go b/cmd/template_cmd.go index 4e97873..11f7375 100644 --- a/cmd/template_cmd.go +++ b/cmd/template_cmd.go @@ -1,6 +1,8 @@ package cmd import ( + "fmt" + "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/blueprint" "github.com/ingrammicro/concerto/api/types" @@ -39,23 +41,22 @@ func TemplateList(c *cli.Context) error { formatter.PrintFatal("Couldn't receive template data", err) } - labelables := make([]*types.LabelableFields, 0, len(templates)) - for i := range templates { - labelables = append(labelables, &templates[i].LabelableFields) + labelables := make([]types.Labelable, len(templates)) + for i, t := range templates { + labelables[i] = types.Labelable(&t) } + labelIDsByName, labelNamesByID := LabelLoadsMapping(c) + filteredLabelables := LabelFiltering(c, labelables, labelIDsByName) + LabelAssignNamesForIDs(c, filteredLabelables, labelNamesByID) - filteredLabelables := LabelFiltering(c, labelables) - - tmp := templates - templates = nil - if len(filteredLabelables) > 0 { - for _, labelable := range filteredLabelables { - for i := range tmp { - if &tmp[i].LabelableFields == labelable { - templates = append(templates, tmp[i]) - } - } + templates = make([]types.Template, len(filteredLabelables)) + for i, labelable := range labelables { + fw, ok := labelable.(*types.Template) + if !ok { + formatter.PrintFatal("Label filtering returned unexpected result", + fmt.Errorf("expected labelable to be a *types.Template, got a %T", labelable)) } + templates[i] = *fw } if err = formatter.PrintList(templates); err != nil { @@ -75,7 +76,8 @@ func TemplateShow(c *cli.Context) error { formatter.PrintFatal("Couldn't receive template data", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&template.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + template.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*template); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -111,7 +113,8 @@ func TemplateCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create template", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&template.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + template.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*template); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } @@ -136,7 +139,8 @@ func TemplateUpdate(c *cli.Context) error { formatter.PrintFatal("Couldn't update template", err) } - LabelAssignNamesForIDs(c, []*types.LabelableFields{&template.LabelableFields}) + _, labelNamesByID := LabelLoadsMapping(c) + template.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*template); err != nil { formatter.PrintFatal("Couldn't print/format result", err) } From a8670ea641013a53619677dc47878e426435713d Mon Sep 17 00:00:00 2001 From: Pablo Cantera Date: Tue, 19 Mar 2019 13:09:30 +0100 Subject: [PATCH 9/9] Fixed labelable interface resolution (issue #77) Closes #77 --- cmd/firewall_profiles_cmd.go | 13 ++++++++----- cmd/labels_cmd.go | 14 ++++++-------- cmd/scripts_cmd.go | 16 +++++++++------- cmd/servers_cmd.go | 14 ++++++-------- cmd/ssh_profiles_cmd.go | 16 +++++++++------- cmd/template_cmd.go | 15 ++++++++------- 6 files changed, 46 insertions(+), 42 deletions(-) diff --git a/cmd/firewall_profiles_cmd.go b/cmd/firewall_profiles_cmd.go index a3595b1..4c30d9f 100644 --- a/cmd/firewall_profiles_cmd.go +++ b/cmd/firewall_profiles_cmd.go @@ -42,14 +42,14 @@ func FirewallProfileList(c *cli.Context) error { } labelables := make([]types.Labelable, len(firewallProfiles)) - for i, fwp := range firewallProfiles { - labelables[i] = types.Labelable(&fwp) + for i:=0; i< len(firewallProfiles); i++ { + labelables[i] = types.Labelable(&firewallProfiles[i]) } labelIDsByName, labelNamesByID := LabelLoadsMapping(c) filteredLabelables := LabelFiltering(c, labelables, labelIDsByName) LabelAssignNamesForIDs(c, filteredLabelables, labelNamesByID) firewallProfiles = make([]types.FirewallProfile, len(filteredLabelables)) - for i, labelable := range labelables { + for i, labelable := range filteredLabelables { fw, ok := labelable.(*types.FirewallProfile) if !ok { formatter.PrintFatal("Label filtering returned unexpected result", @@ -99,8 +99,11 @@ func FirewallProfileCreate(c *cli.Context) error { if c.String("rules") != "" { firewallProfileIn["rules"] = (*params)["rules"] } + + labelIDsByName, labelNamesByID := LabelLoadsMapping(c) + if c.IsSet("labels") { - labelsIdsArr := LabelResolution(c, c.String("labels")) + labelsIdsArr := LabelResolution(c, c.String("labels"), labelIDsByName) firewallProfileIn["label_ids"] = labelsIdsArr } @@ -108,7 +111,7 @@ func FirewallProfileCreate(c *cli.Context) error { if err != nil { formatter.PrintFatal("Couldn't create firewallProfile", err) } - _, labelNamesByID := LabelLoadsMapping(c) + firewallProfile.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*firewallProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) diff --git a/cmd/labels_cmd.go b/cmd/labels_cmd.go index e97c247..95a68ac 100644 --- a/cmd/labels_cmd.go +++ b/cmd/labels_cmd.go @@ -76,9 +76,7 @@ func LabelFiltering(c *cli.Context, items []types.Labelable, labelIDsByName map[ var filteringLabelIDs []string for _, name := range labelNamesIn { id := labelIDsByName[name] - if id != "" { - filteringLabelIDs = append(filteringLabelIDs, id) - } + filteringLabelIDs = append(filteringLabelIDs, id) } var result []types.Labelable for _, item := range items { @@ -137,18 +135,17 @@ func LabelsUnifyInputNames(labelsNames string, formatter format.Formatter) []str // LabelResolution subcommand function retrieves a labels map(Name<->ID) based on label names received to be processed. // The function evaluates the received labels names (comma separated string); with them, solves the assigned IDs for the given labels names. // If the label name is not available in IMCO yet, it is created. -func LabelResolution(c *cli.Context, labelsNames string) []string { +func LabelResolution(c *cli.Context, labelsNames string, labelIDsByName map[string]string) []string { debugCmdFuncInfo(c) labelsSvc, formatter := WireUpLabel(c) labelNamesIn := LabelsUnifyInputNames(labelsNames, formatter) - labelsMapNameToID, _ := LabelLoadsMapping(c) // Obtain output mapped labels Name<->ID; currently in IMCO platform as well as if creation is required labelsOutMap := make(map[string]string) for _, name := range labelNamesIn { // check if the label already exists in IMCO, creates it if it does not exist - if labelsMapNameToID[name] == "" { + if labelIDsByName[name] == "" { labelPayload := make(map[string]interface{}) labelPayload["name"] = name newLabel, err := labelsSvc.CreateLabel(&labelPayload) @@ -157,7 +154,7 @@ func LabelResolution(c *cli.Context, labelsNames string) []string { } labelsOutMap[name] = newLabel.ID } else { - labelsOutMap[name] = labelsMapNameToID[name] + labelsOutMap[name] = labelIDsByName[name] } } labelsIdsArr := make([]string, 0) @@ -174,7 +171,8 @@ func LabelAdd(c *cli.Context) error { labelsSvc, formatter := WireUpLabel(c) checkRequiredFlags(c, []string{"id", "label"}, formatter) - labelsIdsArr := LabelResolution(c, c.String("label")) + labelIDsByName, _ := LabelLoadsMapping(c) + labelsIdsArr := LabelResolution(c, c.String("label"), labelIDsByName) if len(labelsIdsArr) > 1 { formatter.PrintFatal("Too many label names. Please, Use only one label name", fmt.Errorf("Invalid parameter: %v - %v", c.String("label"), labelsIdsArr)) } diff --git a/cmd/scripts_cmd.go b/cmd/scripts_cmd.go index 2711f3d..0e1386e 100644 --- a/cmd/scripts_cmd.go +++ b/cmd/scripts_cmd.go @@ -43,8 +43,8 @@ func ScriptsList(c *cli.Context) error { } labelables := make([]types.Labelable, len(scripts)) - for i, sc := range scripts { - labelables[i] = types.Labelable(&sc) + for i:=0; i< len(scripts); i++ { + labelables[i] = types.Labelable(&scripts[i]) } labelIDsByName, labelNamesByID := LabelLoadsMapping(c) @@ -52,13 +52,13 @@ func ScriptsList(c *cli.Context) error { LabelAssignNamesForIDs(c, filteredLabelables, labelNamesByID) scripts = make([]types.Script, len(filteredLabelables)) - for i, labelable := range labelables { - fw, ok := labelable.(*types.Script) + for i, labelable := range filteredLabelables { + s, ok := labelable.(*types.Script) if !ok { formatter.PrintFatal("Label filtering returned unexpected result", fmt.Errorf("expected labelable to be a *types.Script, got a %T", labelable)) } - scripts[i] = *fw + scripts[i] = *s } if err = formatter.PrintList(scripts); err != nil { @@ -100,8 +100,11 @@ func ScriptCreate(c *cli.Context) error { if c.String("parameters") != "" { scriptIn["parameters"] = strings.Split(c.String("parameters"), ",") } + + labelIDsByName, labelNamesByID := LabelLoadsMapping(c) + if c.IsSet("labels") { - labelsIdsArr := LabelResolution(c, c.String("labels")) + labelsIdsArr := LabelResolution(c, c.String("labels"), labelIDsByName) scriptIn["label_ids"] = labelsIdsArr } @@ -110,7 +113,6 @@ func ScriptCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create script", err) } - _, labelNamesByID := LabelLoadsMapping(c) script.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*script); err != nil { formatter.PrintFatal("Couldn't print/format result", err) diff --git a/cmd/servers_cmd.go b/cmd/servers_cmd.go index 63a250f..64267ec 100644 --- a/cmd/servers_cmd.go +++ b/cmd/servers_cmd.go @@ -2,7 +2,6 @@ package cmd import ( "fmt" - "github.com/codegangsta/cli" "github.com/ingrammicro/concerto/api/cloud" "github.com/ingrammicro/concerto/api/types" @@ -42,16 +41,14 @@ func ServerList(c *cli.Context) error { } labelables := make([]types.Labelable, len(servers)) - for i, server := range servers { - labelables[i] = types.Labelable(&server) + for i:=0; i< len(servers); i++ { + labelables[i] = types.Labelable(&servers[i]) } - labelIDsByName, labelNamesByID := LabelLoadsMapping(c) filteredLabelables := LabelFiltering(c, labelables, labelIDsByName) LabelAssignNamesForIDs(c, filteredLabelables, labelNamesByID) - servers = make([]types.Server, len(filteredLabelables)) - for i, labelable := range labelables { + for i, labelable := range filteredLabelables { s, ok := labelable.(*types.Server) if !ok { formatter.PrintFatal("Label filtering returned unexpected result", @@ -99,8 +96,10 @@ func ServerCreate(c *cli.Context) error { "cloud_account_id": c.String("cloud_account_id"), } + labelIDsByName, labelNamesByID := LabelLoadsMapping(c) + if c.IsSet("labels") { - labelsIdsArr := LabelResolution(c, c.String("labels")) + labelsIdsArr := LabelResolution(c, c.String("labels"), labelIDsByName) serverIn["label_ids"] = labelsIdsArr } @@ -109,7 +108,6 @@ func ServerCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create server", err) } - _, labelNamesByID := LabelLoadsMapping(c) server.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*server); err != nil { formatter.PrintFatal("Couldn't print/format result", err) diff --git a/cmd/ssh_profiles_cmd.go b/cmd/ssh_profiles_cmd.go index 8aa3e36..a3fb548 100644 --- a/cmd/ssh_profiles_cmd.go +++ b/cmd/ssh_profiles_cmd.go @@ -42,20 +42,20 @@ func SSHProfileList(c *cli.Context) error { } labelables := make([]types.Labelable, len(sshProfiles)) - for i, sshP := range sshProfiles { - labelables[i] = types.Labelable(&sshP) + for i:=0; i< len(sshProfiles); i++ { + labelables[i] = types.Labelable(&sshProfiles[i]) } labelIDsByName, labelNamesByID := LabelLoadsMapping(c) filteredLabelables := LabelFiltering(c, labelables, labelIDsByName) LabelAssignNamesForIDs(c, filteredLabelables, labelNamesByID) sshProfiles = make([]types.SSHProfile, len(filteredLabelables)) - for i, labelable := range labelables { - fw, ok := labelable.(*types.SSHProfile) + for i, labelable := range filteredLabelables { + sshP, ok := labelable.(*types.SSHProfile) if !ok { formatter.PrintFatal("Label filtering returned unexpected result", fmt.Errorf("expected labelable to be a *types.SSHProfile, got a %T", labelable)) } - sshProfiles[i] = *fw + sshProfiles[i] = *sshP } if err = formatter.PrintList(sshProfiles); err != nil { @@ -95,8 +95,11 @@ func SSHProfileCreate(c *cli.Context) error { if c.String("private_key") != "" { sshProfileIn["private_key"] = c.String("private_key") } + + labelIDsByName, labelNamesByID := LabelLoadsMapping(c) + if c.IsSet("labels") { - labelsIdsArr := LabelResolution(c, c.String("labels")) + labelsIdsArr := LabelResolution(c, c.String("labels"), labelIDsByName) sshProfileIn["label_ids"] = labelsIdsArr } @@ -105,7 +108,6 @@ func SSHProfileCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create sshProfile", err) } - _, labelNamesByID := LabelLoadsMapping(c) sshProfile.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*sshProfile); err != nil { formatter.PrintFatal("Couldn't print/format result", err) diff --git a/cmd/template_cmd.go b/cmd/template_cmd.go index 11f7375..9c39cdf 100644 --- a/cmd/template_cmd.go +++ b/cmd/template_cmd.go @@ -42,21 +42,21 @@ func TemplateList(c *cli.Context) error { } labelables := make([]types.Labelable, len(templates)) - for i, t := range templates { - labelables[i] = types.Labelable(&t) + for i:=0; i< len(templates); i++ { + labelables[i] = types.Labelable(&templates[i]) } labelIDsByName, labelNamesByID := LabelLoadsMapping(c) filteredLabelables := LabelFiltering(c, labelables, labelIDsByName) LabelAssignNamesForIDs(c, filteredLabelables, labelNamesByID) templates = make([]types.Template, len(filteredLabelables)) - for i, labelable := range labelables { - fw, ok := labelable.(*types.Template) + for i, labelable := range filteredLabelables { + tpl, ok := labelable.(*types.Template) if !ok { formatter.PrintFatal("Label filtering returned unexpected result", fmt.Errorf("expected labelable to be a *types.Template, got a %T", labelable)) } - templates[i] = *fw + templates[i] = *tpl } if err = formatter.PrintList(templates); err != nil { @@ -103,8 +103,10 @@ func TemplateCreate(c *cli.Context) error { "configuration_attributes": (*params)["configuration_attributes"], } + labelIDsByName, labelNamesByID := LabelLoadsMapping(c) + if c.IsSet("labels") { - labelsIdsArr := LabelResolution(c, c.String("labels")) + labelsIdsArr := LabelResolution(c, c.String("labels"), labelIDsByName) templateIn["label_ids"] = labelsIdsArr } @@ -113,7 +115,6 @@ func TemplateCreate(c *cli.Context) error { formatter.PrintFatal("Couldn't create template", err) } - _, labelNamesByID := LabelLoadsMapping(c) template.FillInLabelNames(labelNamesByID) if err = formatter.PrintItem(*template); err != nil { formatter.PrintFatal("Couldn't print/format result", err)