diff --git a/pkg/api/utils/apiServiceUtils.go b/pkg/api/utils/apiServiceUtils.go index 88a8b175..1bdbb717 100644 --- a/pkg/api/utils/apiServiceUtils.go +++ b/pkg/api/utils/apiServiceUtils.go @@ -5,10 +5,9 @@ import ( "crypto/tls" "encoding/json" "fmt" + "github.com/keptn/go-utils/pkg/api/models" "io/ioutil" "net/http" - - "github.com/keptn/go-utils/pkg/api/models" ) // APIService represents the interface for accessing the configuration service diff --git a/pkg/api/utils/resourceUtils.go b/pkg/api/utils/resourceUtils.go index 66ece412..9bd3129e 100644 --- a/pkg/api/utils/resourceUtils.go +++ b/pkg/api/utils/resourceUtils.go @@ -69,8 +69,8 @@ func (r *ResourceHandler) getHTTPClient() *http.Client { return r.HTTPClient } -// CreateServiceResources creates a service resource -func (r *ResourceHandler) CreateServiceResources(project string, stage string, service string, resources []*models.Resource) (*models.EventContext, *models.Error) { +// CreateResources creates a resource for the specified entity +func (r *ResourceHandler) CreateResources(project string, stage string, service string, resources []*models.Resource) (*models.EventContext, *models.Error) { copiedResources := make([]*models.Resource, len(resources), len(resources)) for i, val := range resources { @@ -86,5 +86,11 @@ func (r *ResourceHandler) CreateServiceResources(project string, stage string, s return nil, buildErrorResponse(err.Error()) } - return post(r.Scheme+"://"+r.BaseURL+"/v1/project/"+project+"/stage/"+stage+"/service/"+service+"/resource", requestStr, r) + if project != "" && stage != "" && service != "" { + return post(r.Scheme+"://"+r.BaseURL+"/v1/project/"+project+"/stage/"+stage+"/service/"+service+"/resource", requestStr, r) + } else if project != "" && stage != "" && service == "" { + return post(r.Scheme+"://"+r.BaseURL+"/v1/project/"+project+"/stage/"+stage+"/resource", requestStr, r) + } else { + return post(r.Scheme+"://"+r.BaseURL+"/v1/project/"+project+"/resource", requestStr, r) + } } diff --git a/pkg/configuration-service/utils/sliUtils.go b/pkg/configuration-service/utils/sliUtils.go new file mode 100644 index 00000000..c785fd18 --- /dev/null +++ b/pkg/configuration-service/utils/sliUtils.go @@ -0,0 +1,84 @@ +package utils + +import ( + "strings" + + "github.com/keptn/go-utils/pkg/configuration-service/models" + "gopkg.in/yaml.v2" +) + +// SLIConfig represents the struct of a SLI file +type SLIConfig struct { + Indicators map[string]string `json:"indicators" yaml:"indicators"` +} + +// GetSLIConfiguration retrieves the SLI configuration for a service considering SLI configuration on stage and project level. +// First, the configuration of project-level is retrieved, which is then overridden by configuration on stage level, +// overridden by configuration on service level. +func (r *ResourceHandler) GetSLIConfiguration(project string, stage string, service string, resourceURI string) (map[string]string, error) { + var res *models.Resource + var err error + SLIs := make(map[string]string) + + // get sli config from project + if project != "" { + res, err = r.GetProjectResource(project, resourceURI) + if err != nil { + // return error except "resource not found" type + if !strings.Contains(err.Error(), "resource not found") { + return nil, err + } + } + SLIs, err = addResourceContentToSLIMap(SLIs, res) + if err != nil { + return nil, err + } + } + + // get sli config from stage + if project != "" && stage != "" { + res, err = r.GetStageResource(project, stage, resourceURI) + if err != nil { + // return error except "resource not found" type + if !strings.Contains(err.Error(), "resource not found") { + return nil, err + } + } + SLIs, err = addResourceContentToSLIMap(SLIs, res) + if err != nil { + return nil, err + } + } + + // get sli config from service + if project != "" && stage != "" && service != "" { + res, err = r.GetServiceResource(project, stage, service, resourceURI) + if err != nil { + // return error except "resource not found" type + if !strings.Contains(err.Error(), "resource not found") { + return nil, err + } + } + SLIs, err = addResourceContentToSLIMap(SLIs, res) + if err != nil { + return nil, err + } + } + + return SLIs, nil +} + +func addResourceContentToSLIMap(SLIs map[string]string, resource *models.Resource) (map[string]string, error) { + if resource != nil { + sliConfig := SLIConfig{} + err := yaml.Unmarshal([]byte(resource.ResourceContent), &sliConfig) + if err != nil { + return nil, err + } + + for key, value := range sliConfig.Indicators { + SLIs[key] = value + } + } + return SLIs, nil +} diff --git a/pkg/configuration-service/utils/sliUtils_test.go b/pkg/configuration-service/utils/sliUtils_test.go new file mode 100644 index 00000000..8e44fa9f --- /dev/null +++ b/pkg/configuration-service/utils/sliUtils_test.go @@ -0,0 +1,60 @@ +package utils + +import ( + "testing" + + "github.com/keptn/go-utils/pkg/configuration-service/models" +) + +// TestAddResourceContentToSLIMap +func TestAddResourceContentToSLIMap(t *testing.T) { + SLIs := make(map[string]string) + resource := &models.Resource{} + resourceURI := "provider/sli.yaml" + resource.ResourceURI = &resourceURI + resource.ResourceContent = `--- +indicators: + error_rate: "builtin:service.errors.total.count:merge(0):avg?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" + response_time_p50: "builtin:service.response.time:merge(0):percentile(50)?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" + response_time_p90: "builtin:service.response.time:merge(0):percentile(90)?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" + response_time_p95: "builtin:service.response.time:merge(0):percentile(95)?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" + throughput: "builtin:service.requestCount.total:merge(0):count?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" +` + SLIs, _ = addResourceContentToSLIMap(SLIs, resource) + + if len(SLIs) != 5 { + t.Errorf("Unexpected lenght of SLI map") + } +} + +// TestAddResourceContentToSLIMap +func TestAddMultipleResourceContentToSLIMap(t *testing.T) { + SLIs := make(map[string]string) + resource := &models.Resource{} + resourceURI := "provider/sli.yaml" + resource.ResourceURI = &resourceURI + resource.ResourceContent = `--- +indicators: + error_rate: "not defined" + response_time_p50: "builtin:service.response.time:merge(0):percentile(50)?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" + response_time_p90: "builtin:service.response.time:merge(0):percentile(90)?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" + response_time_p95: "builtin:service.response.time:merge(0):percentile(95)?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" + throughput: "builtin:service.requestCount.total:merge(0):count?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" +` + SLIs, _ = addResourceContentToSLIMap(SLIs, resource) + + resource.ResourceContent = `--- +indicators: + error_rate: "builtin:service.errors.total.count:merge(0):avg?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" + failure_rate: "builtin:service.requestCount.total:merge(0):count?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" +` + SLIs, _ = addResourceContentToSLIMap(SLIs, resource) + + if len(SLIs) != 6 { + t.Errorf("Unexpected length of SLI map") + } + + if SLIs["error_rate"] != "builtin:service.errors.total.count:merge(0):avg?scope=tag(keptn_project:$PROJECT),tag(keptn_stage:$STAGE),tag(keptn_service:$SERVICE),tag(keptn_deployment:$DEPLOYMENT)" { + t.Errorf("Unexpected value of error_rate SLI") + } +} diff --git a/pkg/events/keptnEvents.go b/pkg/events/keptnEvents.go index 3792dba9..48fbd600 100644 --- a/pkg/events/keptnEvents.go +++ b/pkg/events/keptnEvents.go @@ -96,6 +96,8 @@ type ConfigurationChangeEventData struct { // FileChangesUmbrellaChart provides new content for the umbrella chart. // The key value pairs represent the URI within the chart (i.e. the key) and the new content (i.e. the value). FileChangesUmbrellaChart map[string]string `json:"fileChangesUmbrellaChart,omitempty"` + // Labels contains labels + Labels map[string]string `json:"labels"` } // Canary describes the new configuration in a canary release @@ -122,6 +124,8 @@ type DeploymentFinishedEventData struct { Tag string `json:"tag"` // Image of the new deployed artifact Image string `json:"image"` + // Labels contains labels + Labels map[string]string `json:"labels"` } // TestsFinishedEventData represents the data for a test finished event @@ -140,6 +144,10 @@ type TestsFinishedEventData struct { Start string `json:"start"` // End indicates the end timestamp of the tests End string `json:"end"` + // Labels contains labels + Labels map[string]string `json:"labels"` + // Result shows the status of the test + Result string `json:"result"` } // StartEvaluationEventData represents the data for a test finished event @@ -158,6 +166,8 @@ type StartEvaluationEventData struct { Start string `json:"start"` // End indicates the end timestamp of the tests End string `json:"end"` + // Labels contains labels + Labels map[string]string `json:"labels"` } // EvaluationDoneEventData contains information about evaluation results @@ -175,6 +185,8 @@ type EvaluationDoneEventData struct { TestStrategy string `json:"teststrategy"` // DeploymentStrategy is the deployment strategy DeploymentStrategy string `json:"deploymentstrategy"` + // Labels contains labels + Labels map[string]string `json:"labels"` } type EvaluationDetails struct { @@ -182,6 +194,7 @@ type EvaluationDetails struct { TimeEnd string `json:"timeEnd"` Result string `json:"result"` Score float64 `json:"score"` + SLOFileContent string `json:"sloFileContent"` IndicatorResults []*SLIEvaluationResult `json:"indicatorResults"` } @@ -198,15 +211,16 @@ type SLIResult struct { } type SLIEvaluationResult struct { - Score float64 `json:"score"` - Value *SLIResult `json:"value"` - Violations []*SLIViolation `json:"violations"` - Status string `json:"status"` // pass | warning | fail + Score float64 `json:"score"` + Value *SLIResult `json:"value"` + Targets []*SLITarget `json:"targets"` + Status string `json:"status"` // pass | warning | fail } -type SLIViolation struct { +type SLITarget struct { Criteria string `json:"criteria"` TargetValue float64 `json:"targetValue"` + Violated bool `json:"violated"` } // ProblemEventData represents the data for describing a problem @@ -231,6 +245,8 @@ type ProblemEventData struct { Stage string `json:"stage,omitempty"` // Service is the name of the new service Service string `json:"service,omitempty"` + // Labels contains labels + Labels map[string]string `json:"labels"` } // ConfigureMonitoringEventData represents the data necessary to configure monitoring for a service @@ -261,8 +277,11 @@ type InternalGetSLIEventData struct { TestStrategy string `json:"teststrategy"` // DeploymentStrategy is the deployment strategy DeploymentStrategy string `json:"deploymentstrategy"` + Deployment string `json:"deployment"` Indicators []string `json:"indicators"` CustomFilters []*SLIFilter `json:"customFilters"` + // Labels contains labels + Labels map[string]string `json:"labels"` } // InternalGetSLIDoneEventData contains a list of SLIs and their values @@ -280,4 +299,7 @@ type InternalGetSLIDoneEventData struct { IndicatorValues []*SLIResult `json:"indicatorValues"` // DeploymentStrategy is the deployment strategy DeploymentStrategy string `json:"deploymentstrategy"` + Deployment string `json:"deployment"` + // Labels contains labels + Labels map[string]string `json:"labels"` } diff --git a/pkg/mongodb-datastore/models/contenttype.go b/pkg/mongodb-datastore/models/contenttype.go new file mode 100644 index 00000000..1ab19ad2 --- /dev/null +++ b/pkg/mongodb-datastore/models/contenttype.go @@ -0,0 +1,19 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" +) + +// Contenttype contenttype +// swagger:model contenttype +type Contenttype string + +// Validate validates this contenttype +func (m Contenttype) Validate(formats strfmt.Registry) error { + return nil +} diff --git a/pkg/mongodb-datastore/models/data.go b/pkg/mongodb-datastore/models/data.go new file mode 100644 index 00000000..3c7d4098 --- /dev/null +++ b/pkg/mongodb-datastore/models/data.go @@ -0,0 +1,10 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +// Data data +// swagger:model data +type Data interface{} diff --git a/pkg/mongodb-datastore/models/event.go b/pkg/mongodb-datastore/models/event.go new file mode 100644 index 00000000..85d36c48 --- /dev/null +++ b/pkg/mongodb-datastore/models/event.go @@ -0,0 +1,179 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" + + "github.com/go-openapi/errors" + "github.com/go-openapi/swag" +) + +// Event event +// swagger:model event +type Event struct { + + // contenttype + Contenttype Contenttype `json:"contenttype,omitempty"` + + // data + Data Data `json:"data,omitempty"` + + // extensions + Extensions Extensions `json:"extensions,omitempty"` + + // id + // Required: true + ID ID `json:"id"` + + // source + // Required: true + Source Source `json:"source"` + + // specversion + // Required: true + Specversion Specversion `json:"specversion"` + + // time + // Format: date-time + Time Time `json:"time,omitempty"` + + // type + // Required: true + Type Type `json:"type"` +} + +// Validate validates this event +func (m *Event) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateContenttype(formats); err != nil { + res = append(res, err) + } + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSource(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSpecversion(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTime(formats); err != nil { + res = append(res, err) + } + + if err := m.validateType(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Event) validateContenttype(formats strfmt.Registry) error { + + if swag.IsZero(m.Contenttype) { // not required + return nil + } + + if err := m.Contenttype.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("contenttype") + } + return err + } + + return nil +} + +func (m *Event) validateID(formats strfmt.Registry) error { + + if err := m.ID.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("id") + } + return err + } + + return nil +} + +func (m *Event) validateSource(formats strfmt.Registry) error { + + if err := m.Source.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("source") + } + return err + } + + return nil +} + +func (m *Event) validateSpecversion(formats strfmt.Registry) error { + + if err := m.Specversion.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("specversion") + } + return err + } + + return nil +} + +func (m *Event) validateTime(formats strfmt.Registry) error { + + if swag.IsZero(m.Time) { // not required + return nil + } + + if err := m.Time.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("time") + } + return err + } + + return nil +} + +func (m *Event) validateType(formats strfmt.Registry) error { + + if err := m.Type.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("type") + } + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *Event) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Event) UnmarshalBinary(b []byte) error { + var res Event + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/mongodb-datastore/models/extensions.go b/pkg/mongodb-datastore/models/extensions.go new file mode 100644 index 00000000..d098318b --- /dev/null +++ b/pkg/mongodb-datastore/models/extensions.go @@ -0,0 +1,10 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +// Extensions extensions +// swagger:model extensions +type Extensions interface{} diff --git a/pkg/mongodb-datastore/models/id.go b/pkg/mongodb-datastore/models/id.go new file mode 100644 index 00000000..7ff8ceed --- /dev/null +++ b/pkg/mongodb-datastore/models/id.go @@ -0,0 +1,19 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" +) + +// ID id +// swagger:model id +type ID string + +// Validate validates this id +func (m ID) Validate(formats strfmt.Registry) error { + return nil +} diff --git a/pkg/mongodb-datastore/models/keptn_context_extended_c_e.go b/pkg/mongodb-datastore/models/keptn_context_extended_c_e.go index 3fedc286..f2540a81 100644 --- a/pkg/mongodb-datastore/models/keptn_context_extended_c_e.go +++ b/pkg/mongodb-datastore/models/keptn_context_extended_c_e.go @@ -10,41 +10,12 @@ import ( "github.com/go-openapi/errors" "github.com/go-openapi/swag" - "github.com/go-openapi/validate" ) // KeptnContextExtendedCE keptn context extended c e // swagger:model KeptnContextExtendedCE type KeptnContextExtendedCE struct { - - // contenttype - Contenttype string `json:"contenttype,omitempty"` - - // data - Data interface{} `json:"data,omitempty"` - - // extensions - Extensions interface{} `json:"extensions,omitempty"` - - // id - // Required: true - ID *string `json:"id"` - - // source - // Required: true - Source *string `json:"source"` - - // specversion - // Required: true - Specversion *string `json:"specversion"` - - // time - // Format: date-time - Time strfmt.DateTime `json:"time,omitempty"` - - // type - // Required: true - Type *string `json:"type"` + Event // shkeptncontext Shkeptncontext string `json:"shkeptncontext,omitempty"` @@ -53,42 +24,11 @@ type KeptnContextExtendedCE struct { // UnmarshalJSON unmarshals this object from a JSON structure func (m *KeptnContextExtendedCE) UnmarshalJSON(raw []byte) error { // AO0 - var dataAO0 struct { - Contenttype string `json:"contenttype,omitempty"` - - Data interface{} `json:"data,omitempty"` - - Extensions interface{} `json:"extensions,omitempty"` - - ID *string `json:"id"` - - Source *string `json:"source"` - - Specversion *string `json:"specversion"` - - Time strfmt.DateTime `json:"time,omitempty"` - - Type *string `json:"type"` - } - if err := swag.ReadJSON(raw, &dataAO0); err != nil { + var aO0 Event + if err := swag.ReadJSON(raw, &aO0); err != nil { return err } - - m.Contenttype = dataAO0.Contenttype - - m.Data = dataAO0.Data - - m.Extensions = dataAO0.Extensions - - m.ID = dataAO0.ID - - m.Source = dataAO0.Source - - m.Specversion = dataAO0.Specversion - - m.Time = dataAO0.Time - - m.Type = dataAO0.Type + m.Event = aO0 // AO1 var dataAO1 struct { @@ -107,45 +47,11 @@ func (m *KeptnContextExtendedCE) UnmarshalJSON(raw []byte) error { func (m KeptnContextExtendedCE) MarshalJSON() ([]byte, error) { _parts := make([][]byte, 0, 2) - var dataAO0 struct { - Contenttype string `json:"contenttype,omitempty"` - - Data interface{} `json:"data,omitempty"` - - Extensions interface{} `json:"extensions,omitempty"` - - ID *string `json:"id"` - - Source *string `json:"source"` - - Specversion *string `json:"specversion"` - - Time strfmt.DateTime `json:"time,omitempty"` - - Type *string `json:"type"` - } - - dataAO0.Contenttype = m.Contenttype - - dataAO0.Data = m.Data - - dataAO0.Extensions = m.Extensions - - dataAO0.ID = m.ID - - dataAO0.Source = m.Source - - dataAO0.Specversion = m.Specversion - - dataAO0.Time = m.Time - - dataAO0.Type = m.Type - - jsonDataAO0, errAO0 := swag.WriteJSON(dataAO0) - if errAO0 != nil { - return nil, errAO0 + aO0, err := swag.WriteJSON(m.Event) + if err != nil { + return nil, err } - _parts = append(_parts, jsonDataAO0) + _parts = append(_parts, aO0) var dataAO1 struct { Shkeptncontext string `json:"shkeptncontext,omitempty"` @@ -166,23 +72,8 @@ func (m KeptnContextExtendedCE) MarshalJSON() ([]byte, error) { func (m *KeptnContextExtendedCE) Validate(formats strfmt.Registry) error { var res []error - if err := m.validateID(formats); err != nil { - res = append(res, err) - } - - if err := m.validateSource(formats); err != nil { - res = append(res, err) - } - - if err := m.validateSpecversion(formats); err != nil { - res = append(res, err) - } - - if err := m.validateTime(formats); err != nil { - res = append(res, err) - } - - if err := m.validateType(formats); err != nil { + // validation for a type composition with Event + if err := m.Event.Validate(formats); err != nil { res = append(res, err) } @@ -192,55 +83,6 @@ func (m *KeptnContextExtendedCE) Validate(formats strfmt.Registry) error { return nil } -func (m *KeptnContextExtendedCE) validateID(formats strfmt.Registry) error { - - if err := validate.Required("id", "body", m.ID); err != nil { - return err - } - - return nil -} - -func (m *KeptnContextExtendedCE) validateSource(formats strfmt.Registry) error { - - if err := validate.Required("source", "body", m.Source); err != nil { - return err - } - - return nil -} - -func (m *KeptnContextExtendedCE) validateSpecversion(formats strfmt.Registry) error { - - if err := validate.Required("specversion", "body", m.Specversion); err != nil { - return err - } - - return nil -} - -func (m *KeptnContextExtendedCE) validateTime(formats strfmt.Registry) error { - - if swag.IsZero(m.Time) { // not required - return nil - } - - if err := validate.FormatOf("time", "body", "date-time", m.Time.String(), formats); err != nil { - return err - } - - return nil -} - -func (m *KeptnContextExtendedCE) validateType(formats strfmt.Registry) error { - - if err := validate.Required("type", "body", m.Type); err != nil { - return err - } - - return nil -} - // MarshalBinary interface implementation func (m *KeptnContextExtendedCE) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/pkg/mongodb-datastore/models/log_entry.go b/pkg/mongodb-datastore/models/log_entry.go new file mode 100644 index 00000000..bb965638 --- /dev/null +++ b/pkg/mongodb-datastore/models/log_entry.go @@ -0,0 +1,83 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" + + "github.com/go-openapi/errors" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// LogEntry log entry +// swagger:model LogEntry +type LogEntry struct { + + // event Id + EventID string `json:"eventId,omitempty"` + + // keptn context + KeptnContext string `json:"keptnContext,omitempty"` + + // keptn service + KeptnService string `json:"keptnService,omitempty"` + + // log level + LogLevel string `json:"logLevel,omitempty"` + + // message + Message string `json:"message,omitempty"` + + // timestamp + // Format: date-time + Timestamp strfmt.DateTime `json:"timestamp,omitempty"` +} + +// Validate validates this log entry +func (m *LogEntry) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateTimestamp(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *LogEntry) validateTimestamp(formats strfmt.Registry) error { + + if swag.IsZero(m.Timestamp) { // not required + return nil + } + + if err := validate.FormatOf("timestamp", "body", "date-time", m.Timestamp.String(), formats); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *LogEntry) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *LogEntry) UnmarshalBinary(b []byte) error { + var res LogEntry + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/mongodb-datastore/models/source.go b/pkg/mongodb-datastore/models/source.go new file mode 100644 index 00000000..9ef1769a --- /dev/null +++ b/pkg/mongodb-datastore/models/source.go @@ -0,0 +1,19 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" +) + +// Source source +// swagger:model source +type Source string + +// Validate validates this source +func (m Source) Validate(formats strfmt.Registry) error { + return nil +} diff --git a/pkg/mongodb-datastore/models/specversion.go b/pkg/mongodb-datastore/models/specversion.go new file mode 100644 index 00000000..5dea0caf --- /dev/null +++ b/pkg/mongodb-datastore/models/specversion.go @@ -0,0 +1,19 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" +) + +// Specversion specversion +// swagger:model specversion +type Specversion string + +// Validate validates this specversion +func (m Specversion) Validate(formats strfmt.Registry) error { + return nil +} diff --git a/pkg/mongodb-datastore/models/time.go b/pkg/mongodb-datastore/models/time.go new file mode 100644 index 00000000..fbc3e48d --- /dev/null +++ b/pkg/mongodb-datastore/models/time.go @@ -0,0 +1,60 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" + + "github.com/go-openapi/errors" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// Time time +// swagger:model time +type Time strfmt.DateTime + +// UnmarshalJSON sets a Time value from JSON input +func (m *Time) UnmarshalJSON(b []byte) error { + return ((*strfmt.DateTime)(m)).UnmarshalJSON(b) +} + +// MarshalJSON retrieves a Time value as JSON output +func (m Time) MarshalJSON() ([]byte, error) { + return (strfmt.DateTime(m)).MarshalJSON() +} + +// Validate validates this time +func (m Time) Validate(formats strfmt.Registry) error { + var res []error + + if err := validate.FormatOf("", "body", "date-time", strfmt.DateTime(m).String(), formats); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// MarshalBinary interface implementation +func (m *Time) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Time) UnmarshalBinary(b []byte) error { + var res Time + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/mongodb-datastore/models/type.go b/pkg/mongodb-datastore/models/type.go new file mode 100644 index 00000000..80ec79f8 --- /dev/null +++ b/pkg/mongodb-datastore/models/type.go @@ -0,0 +1,19 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" +) + +// Type type +// swagger:model type +type Type string + +// Validate validates this type +func (m Type) Validate(formats strfmt.Registry) error { + return nil +} diff --git a/pkg/mongodb-datastore/utils/eventUtils_test.go b/pkg/mongodb-datastore/utils/eventUtils_test.go index de3ab65f..7068e68a 100644 --- a/pkg/mongodb-datastore/utils/eventUtils_test.go +++ b/pkg/mongodb-datastore/utils/eventUtils_test.go @@ -7,6 +7,8 @@ import ( "net/http/httptest" "testing" + strfmt "github.com/go-openapi/strfmt" + keptnevents "github.com/keptn/go-utils/pkg/events" "github.com/magiconair/properties/assert" ) @@ -37,7 +39,7 @@ func TestGetEventStatusOK(t *testing.T) { "events":[ {"contenttype":"application/json", "data":{"deploymentstrategy":"blue_green_service", - "evaluationdetails":[{"Key":"result","Value":"pass"}], + "evaluationdetails":{"result": "pass"}, "evaluationpassed":true, "project":"sockshop", "service":"carts", @@ -52,7 +54,7 @@ func TestGetEventStatusOK(t *testing.T) { {"contenttype":"application/json", "data":{"deploymentstrategy":"blue_green_service", - "evaluationdetails":[{"Key":"result","Value":"pass"}], + "evaluationdetails":{"result": "pass"}, "evaluationpassed":true, "project":"sockshop", "service":"carts", @@ -67,7 +69,7 @@ func TestGetEventStatusOK(t *testing.T) { {"contenttype":"application/json", "data":{"deploymentstrategy":"direct", - "evaluationdetails":[{"Key":"result","Value":"pass"}], + "evaluationdetails":{"result": "pass"}, "evaluationpassed":true, "project":"sockshop", "service":"carts", @@ -99,7 +101,7 @@ func TestGetEventStatusOK(t *testing.T) { } // check whether the last event is returned - if cloudEvent.Time.String() != "2019-10-21T14:12:48.000Z" { + if strfmt.DateTime(cloudEvent.Time).String() != "2019-10-21T14:12:48.000Z" { t.Error("did not receive the latest event") } diff --git a/pkg/utils/helmUtils.go b/pkg/utils/helmUtils.go index 1c50e1ee..9dc7af43 100644 --- a/pkg/utils/helmUtils.go +++ b/pkg/utils/helmUtils.go @@ -98,7 +98,11 @@ func GetRenderedDeployments(ch *chart.Chart) ([]*appsv1.Deployment, error) { Time: timeconv.Now(), }, } - + ch.Values.Raw += ` +keptn: + project: prj, + service: svc, + deployment: dpl` renderedTemplates, err := renderutil.Render(ch, ch.Values, renderOpts) if err != nil { return nil, err @@ -139,7 +143,11 @@ func GetRenderedServices(ch *chart.Chart) ([]*corev1.Service, error) { Time: timeconv.Now(), }, } - + ch.Values.Raw += ` +keptn: + project: prj, + service: svc, + deployment: dpl` renderedTemplates, err := renderutil.Render(ch, ch.Values, renderOpts) if err != nil { return nil, err diff --git a/pkg/utils/keptnUtils.go b/pkg/utils/keptnUtils.go index c4e7db21..4cc6f957 100644 --- a/pkg/utils/keptnUtils.go +++ b/pkg/utils/keptnUtils.go @@ -1,9 +1,12 @@ package utils import ( + "log" + "regexp" + + "github.com/keptn/go-utils/pkg/configuration-service/utils" "github.com/keptn/go-utils/pkg/models" "gopkg.in/yaml.v2" - "github.com/keptn/go-utils/pkg/configuration-service/utils" ) // KeptnHandler provides an interface to keptn resources @@ -32,3 +35,17 @@ func (k *KeptnHandler) GetShipyard(project string) (*models.Shipyard, error) { } return &shipyard, nil } + +// ValidateKeptnEntityName checks whether the provided name represents a valid +// project, service, or stage name +func ValidateKeptnEntityName(name string) bool { + if len(name) == 0 { + return false + } + reg, err := regexp.Compile(`(^[a-z][a-z0-9-]*[a-z0-9]$)|(^[a-z][a-z0-9]*)`) + if err != nil { + log.Fatal(err) + } + processedString := reg.FindString(name) + return len(processedString) == len(name) +} diff --git a/pkg/utils/keptnUtils_test.go b/pkg/utils/keptnUtils_test.go new file mode 100644 index 00000000..85c9de6f --- /dev/null +++ b/pkg/utils/keptnUtils_test.go @@ -0,0 +1,56 @@ +package utils + +import ( + "math/rand" + "testing" + "time" +) + +// generateStringWithSpecialChars generates a string of the given length +// and containing at least one special character and digit. +func generateStringWithSpecialChars(length int) string { + rand.Seed(time.Now().UnixNano()) + + digits := "0123456789" + specials := "~=+%^*/()[]{}/!@#$?|" + all := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "abcdefghijklmnopqrstuvwxyz" + + digits + specials + + buf := make([]byte, length) + buf[0] = digits[rand.Intn(len(digits))] + buf[1] = specials[rand.Intn(len(specials))] + + for i := 2; i < length; i++ { + buf[i] = all[rand.Intn(len(all))] + } + + rand.Shuffle(len(buf), func(i, j int) { + buf[i], buf[j] = buf[j], buf[i] + }) + + str := string(buf) + + return str +} + +// TestInvalidKeptnEntityName tests whether a random string containing a special character or digit +// does not pass the name validation. +func TestInvalidKeptnEntityName(t *testing.T) { + invalidName := generateStringWithSpecialChars(8) + if ValidateKeptnEntityName(invalidName) { + t.Fatalf("%s starts with upper case letter(s) or contains special character(s), but passed the name validation", invalidName) + } +} + +func TestInvalidKeptnEntityName2(t *testing.T) { + if ValidateKeptnEntityName("sockshop-") { + t.Fatalf("project name must not end with hyphen") + } +} + +func TestValidKeptnEntityName(t *testing.T) { + if !ValidateKeptnEntityName("sockshop-test") { + t.Fatalf("project should be valid") + } +} diff --git a/pkg/utils/kubeUtils.go b/pkg/utils/kubeUtils.go index 0a64ce8f..2e81d655 100644 --- a/pkg/utils/kubeUtils.go +++ b/pkg/utils/kubeUtils.go @@ -14,6 +14,8 @@ import ( typesv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apierr "k8s.io/apimachinery/pkg/api/errors" + // Initialize all known client auth plugins. _ "github.com/Azure/go-autorest/autorest" _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -149,7 +151,9 @@ func WaitForDeploymentsInNamespace(useInClusterConfig bool, namespace string) er } deps, err := clientset.AppsV1().Deployments(namespace).List(metav1.ListOptions{}) for _, dep := range deps.Items { - WaitForDeploymentToBeRolledOut(useInClusterConfig, dep.Name, namespace) + if err := WaitForDeploymentToBeRolledOut(useInClusterConfig, dep.Name, namespace); err != nil { + return err + } } return nil } @@ -209,3 +213,31 @@ func GetKeptnDomain(useInClusterConfig bool) (string, error) { } return cm.Data["app_domain"], nil } + +// CreateNamespace creates a new Kubernetes namespace with the provided name +func CreateNamespace(useInClusterConfig bool, namespace string) error { + + ns := &typesv1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} + clientset, err := GetClientset(useInClusterConfig) + if err != nil { + return err + } + _, err = clientset.CoreV1().Namespaces().Create(ns) + return err +} + +// ExistsNamespace checks whether a namespace with the provided name exists +func ExistsNamespace(useInClusterConfig bool, namespace string) (bool, error) { + clientset, err := GetClientset(useInClusterConfig) + if err != nil { + return false, err + } + _, err = clientset.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{}) + if err != nil { + if statusErr, ok := err.(*apierr.StatusError); ok && statusErr.ErrStatus.Reason == metav1.StatusReasonNotFound { + return false, nil + } + return false, err + } + return true, nil +} diff --git a/releasenotes/releasenotes_V0.4.0.md b/releasenotes/releasenotes_V0.4.0.md new file mode 100644 index 00000000..ab419967 --- /dev/null +++ b/releasenotes/releasenotes_V0.4.0.md @@ -0,0 +1,10 @@ +# Release Notes 0.4.0 + +## New Features +- Evaluation Done events contain more info about SLIs and SLOs [#1058](https://github.com/keptn/keptn/issues/1058) +- Flattened events in MongoDB [#1061](https://github.com/keptn/keptn/issues/1061) +- Add testStrategy and deploymentStrategy in several events [#1098](https://github.com/keptn/keptn/issues/1098) + + +## Fixed Issues +- Fixed an endless loop when fetching resources [#1043](https://github.com/keptn/keptn/issues/1043) diff --git a/releasenotes/releasenotes_V0.5.0.md b/releasenotes/releasenotes_V0.5.0.md new file mode 100644 index 00000000..58df18ea --- /dev/null +++ b/releasenotes/releasenotes_V0.5.0.md @@ -0,0 +1,7 @@ +# Release Notes 0.5.0 + +## New Features +- Added deployment-type to get-sli events [#1161](https://github.com/keptn/keptn/issues/1161) +- Always set host to `api.keptn` when sending cluster-internal API requests [#1167](https://github.com/keptn/keptn/issues/1167) +- Added `label` property to all events involved in the CD workflow [#1147](https://github.com/keptn/keptn/issues/1147) + diff --git a/version b/version index 9325c3cc..8f0916f7 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.3.0 \ No newline at end of file +0.5.0