Skip to content
This repository has been archived by the owner on Dec 21, 2023. It is now read-only.

Commit

Permalink
Merge pull request #134 from keptn/release-0.6.0
Browse files Browse the repository at this point in the history
Release 0.6.0
  • Loading branch information
bacherfl authored Jan 21, 2020
2 parents d3b1e2b + 0b6c3d8 commit d4aa3dc
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 12 deletions.
12 changes: 9 additions & 3 deletions pkg/api/utils/resourceUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
}
84 changes: 84 additions & 0 deletions pkg/configuration-service/utils/sliUtils.go
Original file line number Diff line number Diff line change
@@ -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
}
60 changes: 60 additions & 0 deletions pkg/configuration-service/utils/sliUtils_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
12 changes: 7 additions & 5 deletions pkg/events/keptnEvents.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ type ConfigurationChangeEventData struct {
// FileChangesGeneratedChart provides new content for the generated chart.
// The key value pairs represent the URI within the chart (i.e. the key) and the new content (i.e. the value).
FileChangesGeneratedChart map[string]string `json:"fileChangesGeneratedChart,omitempty"`
// FileChangesUmbrellaChart provides new content for the umbrealla chart.
// 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:"fileChangesUmbreallaChart,omitempty"`
FileChangesUmbrellaChart map[string]string `json:"fileChangesUmbrellaChart,omitempty"`
// Labels contains labels
Labels map[string]string `json:"labels"`
}
Expand Down Expand Up @@ -146,6 +146,8 @@ type TestsFinishedEventData struct {
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
Expand Down Expand Up @@ -234,7 +236,7 @@ type ProblemEventData struct {
// PID is a unique system identifier of the reported problem.
PID string `json:"PID"`
// ImpcatedEntity is an identifier of the impacted entity
ImpactedEntity string `json:"ImpactedEntities,omitempty"`
ImpactedEntity string `json:"ImpactedEntity,omitempty"`
// Tags is a comma separated list of tags that are defined for all impacted entities.
Tags string `json:"Tags,omitempty"`
// Project is the name of the project
Expand Down Expand Up @@ -275,7 +277,7 @@ type InternalGetSLIEventData struct {
TestStrategy string `json:"teststrategy"`
// DeploymentStrategy is the deployment strategy
DeploymentStrategy string `json:"deploymentstrategy"`
Deployment string `json:"deployment"`
Deployment string `json:"deployment"`
Indicators []string `json:"indicators"`
CustomFilters []*SLIFilter `json:"customFilters"`
// Labels contains labels
Expand All @@ -297,7 +299,7 @@ type InternalGetSLIDoneEventData struct {
IndicatorValues []*SLIResult `json:"indicatorValues"`
// DeploymentStrategy is the deployment strategy
DeploymentStrategy string `json:"deploymentstrategy"`
Deployment string `json:"deployment"`
Deployment string `json:"deployment"`
// Labels contains labels
Labels map[string]string `json:"labels"`
}
12 changes: 10 additions & 2 deletions pkg/utils/helmUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
19 changes: 18 additions & 1 deletion pkg/utils/keptnUtils.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
}
56 changes: 56 additions & 0 deletions pkg/utils/keptnUtils_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
30 changes: 30 additions & 0 deletions pkg/utils/kubeUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -211,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
}
8 changes: 8 additions & 0 deletions releasenotes/releasenotes_V0.6.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Release Notes 0.6.0

## New Features
- Added result property to `TestsFinishedEventData` [#542](https://github.com/keptn/keptn/issues/542)
- Added method for validating Keptn entity name [#1261](https://github.com/keptn/keptn/issues/1261)
- Added utility to create namespaces [#1231](https://github.com/keptn/keptn/issues/1231)
- Added helper function to get SLI config for service considering stage and project configs [#1192](https://github.com/keptn/keptn/issues/1192)

2 changes: 1 addition & 1 deletion version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.5.0
0.6.0

0 comments on commit d4aa3dc

Please sign in to comment.