Skip to content

Commit

Permalink
feat: add warnings package
Browse files Browse the repository at this point in the history
  • Loading branch information
l-qing committed Mar 6, 2024
1 parent 2b882aa commit cfbf3f7
Show file tree
Hide file tree
Showing 8 changed files with 415 additions and 0 deletions.
60 changes: 60 additions & 0 deletions warnings/condition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Copyright 2024 The Katanomi Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package warnings

import (
"bytes"
"strconv"

corev1 "k8s.io/api/core/v1"
"knative.dev/pkg/apis"
)

const (
// WarningConditionType represents a warning condition
WarningConditionType apis.ConditionType = "Warning"
)

// NewWarningCondition creates a warning condition from the given warnings
func NewWarningCondition(warnings []WarningRecord) (condition *apis.Condition) {
if len(warnings) == 0 {
return
}

condition = &apis.Condition{
Type: WarningConditionType,
Status: corev1.ConditionTrue,
Severity: apis.ConditionSeverityWarning,
}

if len(warnings) == 1 {
condition.Reason = warnings[0].Reason
condition.Message = warnings[0].Message
return
}

condition.Reason = MultipleWarningsReason
var message bytes.Buffer
for i := range warnings {
message.WriteString(strconv.Itoa(i + 1))
message.WriteString(". ")
message.WriteString(warnings[i].Message)
message.WriteString("\n")
}
condition.Message = message.String()
return
}
59 changes: 59 additions & 0 deletions warnings/condition_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
Copyright 2024 The Katanomi Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package warnings

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"knative.dev/pkg/apis"
)

var _ = Describe("Test.NewWarningCondition", func() {
DescribeTable("should create a warning condition",
func(warnings []WarningRecord, expectedCondition *apis.Condition) {
condition := NewWarningCondition(warnings)
Expect(condition).To(Equal(expectedCondition))
},
Entry("should return nil when warnings is empty", []WarningRecord{}, nil),
Entry("should set reason, message, type, status, and severity when there is only one warning",
[]WarningRecord{
{Condition: apis.Condition{Reason: "reason1", Message: "message1"}},
},
&apis.Condition{
Type: WarningConditionType,
Status: corev1.ConditionTrue,
Severity: apis.ConditionSeverityWarning,
Reason: "reason1",
Message: "message1",
},
),
Entry("should set reason, message, type, status, and severity when there are multiple warnings",
[]WarningRecord{
{Condition: apis.Condition{Reason: "reason1", Message: "message1"}},
{Condition: apis.Condition{Reason: "reason2", Message: "message2"}},
},
&apis.Condition{
Type: WarningConditionType,
Status: corev1.ConditionTrue,
Severity: apis.ConditionSeverityWarning,
Reason: MultipleWarningsReason,
Message: "1. message1\n2. message2\n",
},
),
)
})
26 changes: 26 additions & 0 deletions warnings/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Copyright 2024 The Katanomi Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package warnings contains useful functions to manage warnings in the status
package warnings

const (
// MultipleWarningsReason represent contains multiple warnings
MultipleWarningsReason = "MultipleWarnings"

// DeprecatedClusterTaskReason indicates usage of deprecated ClusterTask
DeprecatedClusterTaskReason = "DeprecatedClusterTask"
)
47 changes: 47 additions & 0 deletions warnings/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright 2024 The Katanomi Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package warnings

import (
duckv1 "knative.dev/pkg/apis/duck/v1"
)

// GetStatusWarnings retrieves warnings from a given status annotation.
func GetStatusWarnings(status *duckv1.Status, annotationKey string) []WarningRecord {
if status == nil || status.Annotations == nil {
return nil
}
return deserializeWarnings(status.Annotations[annotationKey])
}

// EnsureStatusWarning ensures the warning is in the status.
// If the warning already exists, it will be ignored.
func EnsureStatusWarning(status *duckv1.Status, annotationKey string, warning *WarningRecord) []WarningRecord {
if status == nil || warning == nil {
return nil
}
warnings := GetStatusWarnings(status, annotationKey)
warnings = AddWarningIfNotPresent(warnings, warning)
warningStr := serializeWarnings(warnings)
if len(warningStr) != 0 {
if status.Annotations == nil {
status.Annotations = make(map[string]string)
}
status.Annotations[annotationKey] = warningStr
}
return warnings
}
74 changes: 74 additions & 0 deletions warnings/status_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
Copyright 2024 The Katanomi Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package warnings

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
duckv1 "knative.dev/pkg/apis/duck/v1"
)

var _ = Describe("Test.GetStatusWarnings.EnsureStatusWarning", func() {
var (
status *duckv1.Status
key = "key"
warning *WarningRecord
actual, expected []WarningRecord
)

BeforeEach(func() {
// warning = &WarningRecord{}
status = &duckv1.Status{}
})
JustBeforeEach(func() {
actual = EnsureStatusWarning(status, key, warning)
get := GetStatusWarnings(status, key)
Expect(actual).To(Equal(get))
})

When("warning is nil", func() {
It("should not add the warning", func() {
Expect(actual).To(HaveLen(0))
})
})

When("warning is not present", func() {
BeforeEach(func() {
warning = newWarning("warning 1")
expected = []WarningRecord{*warning}
})
It("should add the warning", func() {
Expect(actual).To(HaveLen(1))
Expect(actual).To(Equal(expected))
})
})

When("warning is present", func() {
BeforeEach(func() {
warning = newWarning("warning 1")
expected = []WarningRecord{*warning}
status.Annotations = map[string]string{
key: serializeWarnings(expected),
}
})
It("should not add the warning", func() {
Expect(actual).To(HaveLen(1))
Expect(actual).To(Equal(expected))
})
})

})
68 changes: 68 additions & 0 deletions warnings/warnings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
Copyright 2024 The Katanomi Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package warnings

import (
"encoding/json"
"reflect"

"knative.dev/pkg/apis"
)

// WarningRecord is a record of a warning
type WarningRecord struct {
apis.Condition `json:",inline"`

// Annotations is an unstructured key value map stored with a detail about the warning.
// +optional
Annotations map[string]string `json:"annotations,omitempty"`
}

// AddWarningIfNotPresent adds the warning to the list if it is not present
func AddWarningIfNotPresent(warnings []WarningRecord, warning *WarningRecord) []WarningRecord {
if warning == nil || hasWarning(warnings, warning) {
return warnings
}
warnings = append(warnings, *warning)
return warnings
}

// serializeWarnings serializes the warnings to a json string
func serializeWarnings(warnings []WarningRecord) string {
raw, _ := json.Marshal(warnings)
return string(raw)
}

// deserializeWarnings deserializes the warnings from the raw json string
func deserializeWarnings(raw string) (warnings []WarningRecord) {
if raw == "" {
return
}
warnings = []WarningRecord{}
json.Unmarshal([]byte(raw), &warnings)
return
}

// hasWarning checks if the warning already exists
func hasWarning(warnings []WarningRecord, warning *WarningRecord) bool {
for i := range warnings {
if reflect.DeepEqual(&warnings[i], warning) {
return true
}
}
return false
}
29 changes: 29 additions & 0 deletions warnings/warnings_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright 2024 The Katanomi Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package warnings

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestWarnings(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Warnings Suite")
}
Loading

0 comments on commit cfbf3f7

Please sign in to comment.