diff --git a/.github/workflows/suite.yml b/.github/workflows/suite.yml new file mode 100644 index 000000000..874c3427a --- /dev/null +++ b/.github/workflows/suite.yml @@ -0,0 +1,112 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: suite + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] +env: + ContainerRegistry: "ghcr.io" + ContainerRegistryRepo: "ghcr.io/eclipse-symphony" + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.21 + + - name: Set up custom GOPATH + run: | + mkdir -p /home/runner/go + echo "export GOPATH=/home/runner/go" >> $HOME/.bashrc + echo "export PATH=\$PATH:\$GOPATH/bin" >> $HOME/.bashrc + source $HOME/.bashrc + + - name: Install make + run: sudo apt-get update && sudo apt-get install -y build-essential + + - name: Check docker version and images + run: docker --version && docker images + + - name: Install kubectl + run: | + curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" + chmod +x kubectl + sudo mv ./kubectl /usr/local/bin/kubectl + kubectl version --client + kubectl config view + + - name: Install Helm + run: | + curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 + chmod 700 get_helm.sh + ./get_helm.sh + + - name: Install minikube + run: | + curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 + chmod +x minikube + sudo mv minikube /usr/local/bin/ + minikube start + kubectl config view + + - name: Install Mage + run: | + cd .. + git clone https://github.com/magefile/mage + cd mage + go run bootstrap.go + cd .. + + - name: Install Ginkgo + run: | + go install github.com/onsi/ginkgo/v2/ginkgo@v2.13.1 + export PATH=$PATH:$(go env GOPATH)/bin + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.ContainerRegistry }} + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build docker images + run: | + cd test/localenv/ + mage build:all + mage cluster:up + + - name: Go work init + run: | + mv go.work.bk go.work + + - name: Run ginkgo suite tests + run: | + cd test/integration/scenarios/06.ado/ + ginkgo --cover --junit-report=junit-suite-tests.xml -r + continue-on-error: true + + - name: Dump SymphonyLogs For ginkgo suite tests + run: | + cd test/localenv/ + mage DumpSymphonyLogsForTest ginkgosuite + continue-on-error: true + + - name: Collect and upload symphony test results + uses: actions/upload-artifact@v2 + with: + name: symphony-suite-result + path: | + test/integration/scenarios/06.ado/junit-suite-tests.xml + /tmp/symhony-integration-test-logs/**/*.log + continue-on-error: true + if: always() \ No newline at end of file diff --git a/agent/src/models.rs b/agent/src/models.rs index 2e313edfc..1f16a3060 100644 --- a/agent/src/models.rs +++ b/agent/src/models.rs @@ -42,8 +42,6 @@ pub struct StagedProperties { } #[derive(Serialize, Deserialize)] pub struct CatalogSpec { - #[serde(rename = "siteId")] - site_id: String, #[serde(rename = "type")] pub catalog_type: String, pub properties: StagedProperties, diff --git a/api/Dockerfile b/api/Dockerfile index a558e26f8..5ec417b81 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -38,4 +38,4 @@ EXPOSE 8080 EXPOSE 8081 ENV LOG_LEVEL Debug # ENV CONFIG /symphony-api.json -CMD exec /symphony-api -c $CONFIG -l $LOG_LEVEL \ No newline at end of file +CMD sh -c 'if [ -f /etc/pki/ca-trust/source/anchors/proxy-cert.crt ]; then update-ca-trust; fi && exec /symphony-api -c $CONFIG -l $LOG_LEVEL' diff --git a/api/pkg/apis/v1alpha1/managers/catalogs/catalogs-manager_test.go b/api/pkg/apis/v1alpha1/managers/catalogs/catalogs-manager_test.go index a54bda182..7d769f884 100644 --- a/api/pkg/apis/v1alpha1/managers/catalogs/catalogs-manager_test.go +++ b/api/pkg/apis/v1alpha1/managers/catalogs/catalogs-manager_test.go @@ -32,8 +32,7 @@ var catalogState = model.CatalogState{ Name: "name1", }, Spec: &model.CatalogSpec{ - SiteId: "site1", - Type: "catalog", + Type: "catalog", Properties: map[string]interface{}{ "property1": "value1", "property2": "value2", diff --git a/api/pkg/apis/v1alpha1/managers/solutions/solutions-manager.go b/api/pkg/apis/v1alpha1/managers/solutions/solutions-manager.go index 11b484e75..3432a8547 100644 --- a/api/pkg/apis/v1alpha1/managers/solutions/solutions-manager.go +++ b/api/pkg/apis/v1alpha1/managers/solutions/solutions-manager.go @@ -10,6 +10,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2" @@ -17,11 +18,14 @@ import ( "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/managers" "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers" "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/states" + "github.com/eclipse-symphony/symphony/coa/pkg/logger" observability "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/observability" observ_utils "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/observability/utils" ) +var sLog = logger.NewLogger("coa.runtime") + type SolutionsManager struct { managers.Manager StateProvider states.IStateProvider @@ -48,14 +52,26 @@ func (t *SolutionsManager) DeleteState(ctx context.Context, name string, namespa var err error = nil defer observ_utils.CloseSpanWithError(span, &err) + var rootResource string + var version string + parts := strings.Split(name, ":") + if len(parts) == 2 { + rootResource = parts[0] + version = parts[1] + } else { + return v1alpha2.NewCOAError(nil, fmt.Sprintf("Name is invalid does not match name in request (%s)", name), v1alpha2.BadRequest) + } + + id := rootResource + "-" + version err = t.StateProvider.Delete(ctx, states.DeleteRequest{ - ID: name, + ID: id, Metadata: map[string]interface{}{ - "namespace": namespace, - "group": model.SolutionGroup, - "version": "v1", - "resource": "solutions", - "kind": "Solution", + "namespace": namespace, + "group": model.SolutionGroup, + "version": "v1", + "resource": "solutions", + "kind": "Solution", + "rootResource": rootResource, }, }) return err @@ -68,17 +84,32 @@ func (t *SolutionsManager) UpsertState(ctx context.Context, name string, state m var err error = nil defer observ_utils.CloseSpanWithError(span, &err) + sLog.Info(" M (Solution manager): upsert state >>>>>>>>>>>>>>>>>>>> %v, %v, %v", state.Spec.Version, state.Spec.RootResource, name) if state.ObjectMeta.Name != "" && state.ObjectMeta.Name != name { return v1alpha2.NewCOAError(nil, fmt.Sprintf("Name in metadata (%s) does not match name in request (%s)", state.ObjectMeta.Name, name), v1alpha2.BadRequest) } state.ObjectMeta.FixNames(name) + var rootResource string + version := state.Spec.Version + if state.Spec.RootResource == "" && version != "" { + suffix := "-" + version + rootResource = strings.TrimSuffix(name, suffix) + } else { + rootResource = state.Spec.RootResource + } + + state.ObjectMeta.Labels["rootResource"] = rootResource + state.ObjectMeta.Labels["version"] = version + body := map[string]interface{}{ - "apiVersion": model.SolutionGroup + "/v1", - "kind": "Solution", - "metadata": state.ObjectMeta, - "spec": state.Spec, + "apiVersion": model.SolutionGroup + "/v1", + "kind": "Solution", + "metadata": state.ObjectMeta, + "spec": state.Spec, + "rootResource": rootResource, } + upsertRequest := states.UpsertRequest{ Value: states.StateEntry{ ID: name, @@ -172,3 +203,32 @@ func (t *SolutionsManager) GetState(ctx context.Context, id string, namespace st } return ret, nil } + +func (t *SolutionsManager) GetLatestState(ctx context.Context, id string, namespace string) (model.SolutionState, error) { + ctx, span := observability.StartSpan("Solutions Manager", ctx, &map[string]string{ + "method": "GetLatest", + }) + var err error = nil + defer observ_utils.CloseSpanWithError(span, &err) + + getRequest := states.GetRequest{ + ID: id, + Metadata: map[string]interface{}{ + "version": "v1", + "group": model.SolutionGroup, + "resource": "solutions", + "namespace": namespace, + "kind": "Solution", + }, + } + target, err := t.StateProvider.GetLatest(ctx, getRequest) + if err != nil { + return model.SolutionState{}, err + } + + ret, err := getSolutionState(id, target.Body) + if err != nil { + return model.SolutionState{}, err + } + return ret, nil +} diff --git a/api/pkg/apis/v1alpha1/managers/staging/staging-manager_test.go b/api/pkg/apis/v1alpha1/managers/staging/staging-manager_test.go index f8864c4bd..ce0314022 100644 --- a/api/pkg/apis/v1alpha1/managers/staging/staging-manager_test.go +++ b/api/pkg/apis/v1alpha1/managers/staging/staging-manager_test.go @@ -155,7 +155,6 @@ func InitializeMockSymphonyAPI() *httptest.Server { Name: "catalog1", }, Spec: &model.CatalogSpec{ - SiteId: "fake", Generation: "1", ParentName: "fakeparent", }, diff --git a/api/pkg/apis/v1alpha1/managers/sync/sync-manager_test.go b/api/pkg/apis/v1alpha1/managers/sync/sync-manager_test.go index 61b915e7d..69ff2b17d 100644 --- a/api/pkg/apis/v1alpha1/managers/sync/sync-manager_test.go +++ b/api/pkg/apis/v1alpha1/managers/sync/sync-manager_test.go @@ -115,8 +115,7 @@ func InitiazlizeMockSymphonyAPI(siteId string) *httptest.Server { Name: "catalog1", }, Spec: &model.CatalogSpec{ - SiteId: "parent", - Type: "Instance", + Type: "Instance", Properties: map[string]interface{}{ "foo": "bar", }, diff --git a/api/pkg/apis/v1alpha1/model/campaigncontainer.go b/api/pkg/apis/v1alpha1/model/campaigncontainer.go new file mode 100644 index 000000000..c83928b18 --- /dev/null +++ b/api/pkg/apis/v1alpha1/model/campaigncontainer.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package model + +import ( + "errors" +) + +type CampaignContainerState struct { + ObjectMeta ObjectMeta `json:"metadata,omitempty"` + Spec *CampaignContainerSpec `json:"spec,omitempty"` +} + +type CampaignContainerSpec struct { + Name string `json:"name,omitempty"` +} + +func (c CampaignContainerSpec) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(CampaignContainerSpec) + if !ok { + return false, errors.New("parameter is not a CampaignContainerSpec type") + } + + if c.Name != otherC.Name { + return false, nil + } + + return true, nil +} + +func (c CampaignContainerState) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(CampaignContainerState) + if !ok { + return false, errors.New("parameter is not a CampaignContainerState type") + } + + equal, err := c.ObjectMeta.DeepEquals(otherC.ObjectMeta) + if err != nil || !equal { + return equal, err + } + + equal, err = c.Spec.DeepEquals(*otherC.Spec) + if err != nil || !equal { + return equal, err + } + + return true, nil +} diff --git a/api/pkg/apis/v1alpha1/model/catalog.go b/api/pkg/apis/v1alpha1/model/catalog.go index a311e0377..7a912f779 100644 --- a/api/pkg/apis/v1alpha1/model/catalog.go +++ b/api/pkg/apis/v1alpha1/model/catalog.go @@ -31,7 +31,6 @@ type ObjectRef struct { Metadata map[string]string `json:"metadata,omitempty"` } type CatalogSpec struct { - SiteId string `json:"siteId"` Type string `json:"type"` Metadata map[string]string `json:"metadata,omitempty"` Properties map[string]interface{} `json:"properties"` @@ -50,10 +49,6 @@ func (c CatalogSpec) DeepEquals(other IDeepEquals) (bool, error) { return false, errors.New("parameter is not a CatalogSpec type") } - if c.SiteId != otherC.SiteId { - return false, nil - } - if c.ParentName != otherC.ParentName { return false, nil } diff --git a/api/pkg/apis/v1alpha1/model/catalog_test.go b/api/pkg/apis/v1alpha1/model/catalog_test.go index 50bb0d81e..097807ed4 100644 --- a/api/pkg/apis/v1alpha1/model/catalog_test.go +++ b/api/pkg/apis/v1alpha1/model/catalog_test.go @@ -30,7 +30,6 @@ func TestIntefaceConvertion(t *testing.T) { } func TestCatalogMatch(t *testing.T) { catalog1 := CatalogSpec{ - SiteId: "siteId", ParentName: "parentName", Generation: "1", Properties: map[string]interface{}{ @@ -38,7 +37,6 @@ func TestCatalogMatch(t *testing.T) { }, } catalog2 := CatalogSpec{ - SiteId: "siteId", ParentName: "parentName", Generation: "1", Properties: map[string]interface{}{ @@ -63,25 +61,13 @@ func TestCatalogMatchOneEmpty(t *testing.T) { } func TestCatalogNotMatch(t *testing.T) { - catalog1 := CatalogSpec{ - SiteId: "siteId", - } - catalog2 := CatalogSpec{ - SiteId: "siteId2", - } - - // siteId not match - catalog1.SiteId = "siteId" - catalog2.SiteId = "siteId2" - equal, err := catalog1.DeepEquals(catalog2) - assert.Nil(t, err) - assert.False(t, equal) + catalog1 := CatalogSpec{} + catalog2 := CatalogSpec{} // parentName not match - catalog2.SiteId = "siteId" catalog1.ParentName = "parentName" catalog2.ParentName = "parentName2" - equal, err = catalog1.DeepEquals(catalog2) + equal, err := catalog1.DeepEquals(catalog2) assert.Nil(t, err) assert.False(t, equal) diff --git a/api/pkg/apis/v1alpha1/model/catalogcontainer.go b/api/pkg/apis/v1alpha1/model/catalogcontainer.go new file mode 100644 index 000000000..2ed093ae8 --- /dev/null +++ b/api/pkg/apis/v1alpha1/model/catalogcontainer.go @@ -0,0 +1,53 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package model + +import ( + "errors" +) + +// TODO: all state objects should converge to this paradigm: id, spec and status +type CatalogContainerState struct { + ObjectMeta ObjectMeta `json:"metadata,omitempty"` + Spec *CatalogContainerSpec `json:"spec,omitempty"` +} + +type CatalogContainerSpec struct { + Name string `json:"name"` +} + +func (c CatalogContainerSpec) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(CatalogContainerSpec) + if !ok { + return false, errors.New("parameter is not a CatalogContainerSpec type") + } + + if c.Name != otherC.Name { + return false, nil + } + + return true, nil +} + +func (c CatalogContainerState) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(CatalogContainerState) + if !ok { + return false, errors.New("parameter is not a CatalogContainerState type") + } + + equal, err := c.ObjectMeta.DeepEquals(otherC.ObjectMeta) + if err != nil || !equal { + return equal, err + } + + equal, err = c.Spec.DeepEquals(*otherC.Spec) + if err != nil || !equal { + return equal, err + } + + return true, nil +} diff --git a/api/pkg/apis/v1alpha1/model/instancecontainer.go b/api/pkg/apis/v1alpha1/model/instancecontainer.go new file mode 100644 index 000000000..81e09bee8 --- /dev/null +++ b/api/pkg/apis/v1alpha1/model/instancecontainer.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package model + +import ( + "errors" +) + +type InstanceContainerState struct { + ObjectMeta ObjectMeta `json:"metadata,omitempty"` + Spec *InstanceContainerSpec `json:"spec,omitempty"` +} + +type InstanceContainerSpec struct { + Name string `json:"name,omitempty"` +} + +func (c InstanceContainerSpec) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(InstanceContainerSpec) + if !ok { + return false, errors.New("parameter is not a InstanceContainerSpec type") + } + + if c.Name != otherC.Name { + return false, nil + } + + return true, nil +} + +func (c InstanceContainerState) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(InstanceContainerState) + if !ok { + return false, errors.New("parameter is not a InstanceContainerState type") + } + + equal, err := c.ObjectMeta.DeepEquals(otherC.ObjectMeta) + if err != nil || !equal { + return equal, err + } + + equal, err = c.Spec.DeepEquals(*otherC.Spec) + if err != nil || !equal { + return equal, err + } + + return true, nil +} diff --git a/api/pkg/apis/v1alpha1/model/solution.go b/api/pkg/apis/v1alpha1/model/solution.go index a266d4e51..0826b0f8d 100644 --- a/api/pkg/apis/v1alpha1/model/solution.go +++ b/api/pkg/apis/v1alpha1/model/solution.go @@ -17,9 +17,11 @@ type ( } SolutionSpec struct { - DisplayName string `json:"displayName,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` - Components []ComponentSpec `json:"components,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + Components []ComponentSpec `json:"components,omitempty"` + Version string `json:"version,omitempty"` + RootResource string `json:"rootResource,omitempty"` } ) diff --git a/api/pkg/apis/v1alpha1/model/solutioncontainer.go b/api/pkg/apis/v1alpha1/model/solutioncontainer.go new file mode 100644 index 000000000..c8c325b11 --- /dev/null +++ b/api/pkg/apis/v1alpha1/model/solutioncontainer.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package model + +import ( + "errors" +) + +type SolutionContainerState struct { + ObjectMeta ObjectMeta `json:"metadata,omitempty"` + Spec *SolutionContainerSpec `json:"spec,omitempty"` +} + +type SolutionContainerSpec struct { + Name string `json:"name,omitempty"` +} + +func (c SolutionContainerSpec) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(SolutionContainerSpec) + if !ok { + return false, errors.New("parameter is not a SolutionContainerSpec type") + } + + if c.Name != otherC.Name { + return false, nil + } + + return true, nil +} + +func (c SolutionContainerState) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(SolutionContainerState) + if !ok { + return false, errors.New("parameter is not a SolutionContainerState type") + } + + equal, err := c.ObjectMeta.DeepEquals(otherC.ObjectMeta) + if err != nil || !equal { + return equal, err + } + + equal, err = c.Spec.DeepEquals(*otherC.Spec) + if err != nil || !equal { + return equal, err + } + + return true, nil +} diff --git a/api/pkg/apis/v1alpha1/model/targetcontainer.go b/api/pkg/apis/v1alpha1/model/targetcontainer.go new file mode 100644 index 000000000..ff42fe461 --- /dev/null +++ b/api/pkg/apis/v1alpha1/model/targetcontainer.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package model + +import ( + "errors" +) + +type TargetContainerState struct { + ObjectMeta ObjectMeta `json:"metadata,omitempty"` + Spec *TargetContainerSpec `json:"spec,omitempty"` +} + +type TargetContainerSpec struct { + Name string `json:"name,omitempty"` +} + +func (c TargetContainerSpec) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(TargetContainerSpec) + if !ok { + return false, errors.New("parameter is not a TargetContainerSpec type") + } + + if c.Name != otherC.Name { + return false, nil + } + + return true, nil +} + +func (c TargetContainerState) DeepEquals(other IDeepEquals) (bool, error) { + otherC, ok := other.(TargetContainerState) + if !ok { + return false, errors.New("parameter is not a TargetContainerState type") + } + + equal, err := c.ObjectMeta.DeepEquals(otherC.ObjectMeta) + if err != nil || !equal { + return equal, err + } + + equal, err = c.Spec.DeepEquals(*otherC.Spec) + if err != nil || !equal { + return equal, err + } + + return true, nil +} diff --git a/api/pkg/apis/v1alpha1/providers/stage/materialize/materialize.go b/api/pkg/apis/v1alpha1/providers/stage/materialize/materialize.go index b015de2ce..6e79e1eb6 100644 --- a/api/pkg/apis/v1alpha1/providers/stage/materialize/materialize.go +++ b/api/pkg/apis/v1alpha1/providers/stage/materialize/materialize.go @@ -179,8 +179,8 @@ func (i *MaterializeStageProvider) Process(ctx context.Context, mgrContext conte err = utils.UpsertSolution(ctx, i.Config.BaseUrl, solutionState.ObjectMeta.Name, i.Config.User, i.Config.Password, objectData, solutionState.ObjectMeta.Namespace) if err != nil { mLog.Errorf("Failed to create solution %s: %s", name, err.Error()) - return outputs, false, err } + return outputs, false, err creationCount++ case "target": var targetState model.TargetState diff --git a/api/pkg/apis/v1alpha1/providers/states/k8s/k8s.go b/api/pkg/apis/v1alpha1/providers/states/k8s/k8s.go index 172047984..e54cf0c65 100644 --- a/api/pkg/apis/v1alpha1/providers/states/k8s/k8s.go +++ b/api/pkg/apis/v1alpha1/providers/states/k8s/k8s.go @@ -12,6 +12,7 @@ import ( "fmt" "path/filepath" "strconv" + "time" "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/utils" @@ -180,11 +181,20 @@ func (s *K8sStateProvider) Upsert(ctx context.Context, entry states.UpsertReques version := model.ReadPropertyCompat(entry.Metadata, "version", nil) resource := model.ReadPropertyCompat(entry.Metadata, "resource", nil) kind := model.ReadPropertyCompat(entry.Metadata, "kind", nil) + rootResource := model.ReadPropertyCompat(entry.Metadata, "rootResource", nil) + supportStr := model.ReadPropertyCompat(entry.Metadata, "supportVersioning", nil) if namespace == "" { namespace = "default" } + var supportVersioning bool + supportVersioning, err = strconv.ParseBool(supportStr) + if err != nil { + supportVersioning = false + } + sLog.Info(" P (K8s State): upsert state supportVersioning>>>>>>>>>>>>>>>>>>>> %v , %v", supportVersioning, namespace) + resourceId := schema.GroupVersionResource{ Group: group, Version: version, @@ -196,6 +206,8 @@ func (s *K8sStateProvider) Upsert(ctx context.Context, entry states.UpsertReques return "", err } + sLog.Info(" P (K8s State): upsert state >>>>>>>>>>>>>>>>>>>> %v , %v", entry.Value.ID, namespace) + j, _ := json.Marshal(entry.Value.Body) item, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).Get(ctx, entry.Value.ID, metav1.GetOptions{}) if err != nil { @@ -222,9 +234,46 @@ func (s *K8sStateProvider) Upsert(ctx context.Context, entry states.UpsertReques } unc.SetName(metadata.Name) unc.SetNamespace(metadata.Namespace) - unc.SetLabels(metadata.Labels) unc.SetAnnotations(metadata.Annotations) + if supportVersioning { + latestFilterValue := "tag=latest" + labelSelector := "rootResource=" + rootResource + "," + latestFilterValue + listOptions := metav1.ListOptions{ + LabelSelector: labelSelector, + } + + items, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).List(ctx, listOptions) + if err != nil { + sLog.Errorf(" P (K8s State): failed to list object with labels %s in namespace %s: %v ", labelSelector, namespace, err) + return "", err + } + + if len(items.Items) == 0 { + sLog.Infof(" P (K8s State): no objects found with labels %s in namespace %s: %v ", labelSelector, namespace, err) + } + + for _, v := range items.Items { + labels := v.GetLabels() + delete(labels, "version") + v.SetLabels(labels) + + _, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).Update(ctx, &v, metav1.UpdateOptions{}) + if err != nil { + sLog.Errorf(" P (K8s State): failed to remove labels %s from obj %s in namespace %s: %v ", latestFilterValue, v.GetName(), err) + return "", err + } else { + sLog.Infof(" P (K8s State): remove labels %s from object in namespace %s: %v ", labelSelector, v.GetName(), namespace, err) + } + } + if metadata.Labels == nil { + metadata.Labels = make(map[string]string) + } + + metadata.Labels["tag"] = "latest" + unc.SetLabels(metadata.Labels) + } + _, err = s.DynamicClient.Resource(resourceId).Namespace(namespace).Create(ctx, unc, metav1.CreateOptions{}) if err != nil { sLog.Errorf(" P (K8s State): failed to create object: %v", err) @@ -417,12 +466,15 @@ func (s *K8sStateProvider) Delete(ctx context.Context, request states.DeleteRequ var err error = nil defer observ_utils.CloseSpanWithError(span, &err) - sLog.Info(" P (K8s State): delete state") + sLog.Info(" P (K8s State): delete state %v", request.ID) namespace := model.ReadPropertyCompat(request.Metadata, "namespace", nil) group := model.ReadPropertyCompat(request.Metadata, "group", nil) version := model.ReadPropertyCompat(request.Metadata, "version", nil) resource := model.ReadPropertyCompat(request.Metadata, "resource", nil) + rootResource := model.ReadPropertyCompat(request.Metadata, "rootResource", nil) + + sLog.Info(" P (K8s State): delete state >>>>>>>>>>>>>>>>>>>> %v", request.ID) resourceId := schema.GroupVersionResource{ Group: group, @@ -438,6 +490,42 @@ func (s *K8sStateProvider) Delete(ctx context.Context, request states.DeleteRequ return err } + item, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).Get(ctx, request.ID, metav1.GetOptions{}) + if err == nil { + labels := item.GetLabels() + _, exists := labels["tag"] + + if exists && labels["tag"] == "latest" { + labelSelector := "rootResource=" + rootResource + listOptions := metav1.ListOptions{ + LabelSelector: labelSelector, + } + items, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).List(ctx, listOptions) + if err != nil { + sLog.Errorf(" P (K8s State): failed to list object with labels %s in namespace %s: %v ", labelSelector, namespace, err) + return err + } + + var latestItem unstructured.Unstructured + var latestTime time.Time + for _, v := range items.Items { + if latestTime.Before(v.GetCreationTimestamp().Time) { + latestTime = v.GetCreationTimestamp().Time + latestItem = v + } + } + + latestItem.SetLabels(map[string]string{"tag": "latest"}) + _, err = s.DynamicClient.Resource(resourceId).Namespace(namespace).Update(ctx, &latestItem, metav1.UpdateOptions{}) + if err != nil { + sLog.Errorf(" P (K8s State): failed to add labels for obj %s in namespace %s: %v ", latestItem.GetName(), err) + return err + } else { + sLog.Infof(" P (K8s State): add labels %s from object %s in namespace %s: %v ", labelSelector, latestItem.GetName(), namespace, err) + } + } + } + err = s.DynamicClient.Resource(resourceId).Namespace(namespace).Delete(ctx, request.ID, metav1.DeleteOptions{}) if err != nil { sLog.Errorf(" P (K8s State): failed to delete objects: %v", err) @@ -453,13 +541,15 @@ func (s *K8sStateProvider) Get(ctx context.Context, request states.GetRequest) ( var err error = nil defer observ_utils.CloseSpanWithError(span, &err) - sLog.Info(" P (K8s State): get state") + sLog.Info(" P (K8s State): get state ") namespace := model.ReadPropertyCompat(request.Metadata, "namespace", nil) group := model.ReadPropertyCompat(request.Metadata, "group", nil) version := model.ReadPropertyCompat(request.Metadata, "version", nil) resource := model.ReadPropertyCompat(request.Metadata, "resource", nil) + sLog.Info(" P (K8s State): get state >>>>>>>>>>>>>>>>>>>> %v", request.ID) + if namespace == "" { namespace = "default" } @@ -506,6 +596,85 @@ func (s *K8sStateProvider) Get(ctx context.Context, request states.GetRequest) ( return ret, nil } +func (s *K8sStateProvider) GetLatest(ctx context.Context, request states.GetRequest) (states.StateEntry, error) { + ctx, span := observability.StartSpan("K8s State Provider", ctx, &map[string]string{ + "method": "GetLatest", + }) + var err error = nil + defer observ_utils.CloseSpanWithError(span, &err) + + sLog.Info(" P (K8s State): get state with latest label") + + namespace := model.ReadPropertyCompat(request.Metadata, "namespace", nil) + group := model.ReadPropertyCompat(request.Metadata, "group", nil) + version := model.ReadPropertyCompat(request.Metadata, "version", nil) + resource := model.ReadPropertyCompat(request.Metadata, "resource", nil) + + sLog.Info(" P (K8s State): debug get GetLatest >>>>>>>>>>>>>>>>>>>> %v", request.ID) + + if namespace == "" { + namespace = "default" + } + + resourceId := schema.GroupVersionResource{ + Group: group, + Version: version, + Resource: resource, + } + + if request.ID == "" { + err := v1alpha2.NewCOAError(nil, "found invalid request ID", v1alpha2.BadRequest) + return states.StateEntry{}, err + } + + latestFilterValue := "tag=latest" + labelSelector := "rootResource=" + request.ID + "," + latestFilterValue + options := metav1.ListOptions{ + LabelSelector: labelSelector, + } + + items, err := s.DynamicClient.Resource(resourceId).Namespace(namespace).List(ctx, options) + if err != nil { + sLog.Errorf(" P (K8s State): failed to get latest object %s in namespace %s: %v ", request.ID, namespace, err) + return states.StateEntry{}, err + } + + var latestItem unstructured.Unstructured + var latestTime time.Time + + if len(items.Items) == 0 { + err := v1alpha2.NewCOAError(nil, "failed to find latest object", v1alpha2.NotFound) + return states.StateEntry{}, err + } + + for _, v := range items.Items { + if latestTime.Before(v.GetCreationTimestamp().Time) { + latestTime = v.GetCreationTimestamp().Time + latestItem = v + } + } + + generation := latestItem.GetGeneration() + + metadata := model.ObjectMeta{ + Name: latestItem.GetName(), + Namespace: latestItem.GetNamespace(), + Labels: latestItem.GetLabels(), + Annotations: latestItem.GetAnnotations(), + } + + ret := states.StateEntry{ + ID: request.ID, + ETag: strconv.FormatInt(generation, 10), + Body: map[string]interface{}{ + "spec": latestItem.Object["spec"], + "status": latestItem.Object["status"], + "metadata": metadata, + }, + } + return ret, nil +} + // Implmeement the IConfigProvider interface func (s *K8sStateProvider) Read(object string, field string) (string, error) { obj, err := s.Get(context.TODO(), states.GetRequest{ diff --git a/api/pkg/apis/v1alpha1/providers/target/staging/staging.go b/api/pkg/apis/v1alpha1/providers/target/staging/staging.go index f6d2e6467..a3c848514 100644 --- a/api/pkg/apis/v1alpha1/providers/target/staging/staging.go +++ b/api/pkg/apis/v1alpha1/providers/target/staging/staging.go @@ -178,7 +178,6 @@ func (i *StagingTargetProvider) Apply(ctx context.Context, deployment model.Depl if catalog.Spec == nil { catalog.ObjectMeta.Name = deployment.Instance.ObjectMeta.Name + "-" + i.Config.TargetName catalog.Spec = &model.CatalogSpec{ - SiteId: i.Context.SiteInfo.SiteId, Type: "staged", } } diff --git a/api/pkg/apis/v1alpha1/utils/symphony-api.go b/api/pkg/apis/v1alpha1/utils/symphony-api.go index e10d19911..b17f8a541 100644 --- a/api/pkg/apis/v1alpha1/utils/symphony-api.go +++ b/api/pkg/apis/v1alpha1/utils/symphony-api.go @@ -10,6 +10,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" @@ -436,7 +437,19 @@ func GetSolution(context context.Context, baseUrl string, solution string, user if err != nil { return ret, err } - path := "solutions/" + url.QueryEscape(solution) + + var name string + var version string + + parts := strings.Split(solution, ":") + if len(parts) == 2 { + name = parts[0] + version = parts[1] + } else { + return ret, errors.New("invalid solution name") + } + + path := "solutions/" + url.QueryEscape(name) + url.QueryEscape(version) path = path + "?namespace=" + url.QueryEscape(namespace) response, err := callRestAPI(context, baseUrl, path, "GET", nil, token) if err != nil { @@ -455,7 +468,19 @@ func UpsertSolution(context context.Context, baseUrl string, solution string, us if err != nil { return err } - path := "solutions/" + url.QueryEscape(solution) + + var name string + var version string + + parts := strings.Split(solution, ":") + if len(parts) == 2 { + name = parts[0] + version = parts[1] + } else { + return errors.New("invalid solution name") + } + + path := "solutions/" + url.QueryEscape(name) + url.QueryEscape(version) path = path + "?namespace=" + url.QueryEscape(namespace) _, err = callRestAPI(context, baseUrl, path, "POST", payload, token) if err != nil { @@ -469,7 +494,19 @@ func DeleteSolution(context context.Context, baseUrl string, solution string, us if err != nil { return err } - path := "solutions/" + url.QueryEscape(solution) + + var name string + var version string + + parts := strings.Split(solution, ":") + if len(parts) == 2 { + name = parts[0] + version = parts[1] + } else { + return errors.New("invalid solution name") + } + + path := "solutions/" + url.QueryEscape(name) + url.QueryEscape(version) path = path + "?namespace=" + url.QueryEscape(namespace) _, err = callRestAPI(context, baseUrl, path, "DELETE", nil, token) if err != nil { diff --git a/api/pkg/apis/v1alpha1/vendors/catalogs-vendor_test.go b/api/pkg/apis/v1alpha1/vendors/catalogs-vendor_test.go index 22e5e806a..84852b593 100644 --- a/api/pkg/apis/v1alpha1/vendors/catalogs-vendor_test.go +++ b/api/pkg/apis/v1alpha1/vendors/catalogs-vendor_test.go @@ -28,8 +28,7 @@ var catalogState = model.CatalogState{ Name: "name1", }, Spec: &model.CatalogSpec{ - SiteId: "site1", - Type: "catalog", + Type: "catalog", Properties: map[string]interface{}{ "property1": "value1", "property2": "value2", @@ -174,8 +173,7 @@ func TestCatalogOnCheck(t *testing.T) { var catalogState = model.CatalogState{ Spec: &model.CatalogSpec{ - SiteId: "site1", - Type: "catalog", + Type: "catalog", Properties: map[string]interface{}{ "property1": "value1", "property2": "value2", diff --git a/api/pkg/apis/v1alpha1/vendors/federation-vendor_test.go b/api/pkg/apis/v1alpha1/vendors/federation-vendor_test.go index a8faf1949..f80558b26 100644 --- a/api/pkg/apis/v1alpha1/vendors/federation-vendor_test.go +++ b/api/pkg/apis/v1alpha1/vendors/federation-vendor_test.go @@ -374,8 +374,7 @@ func TestFederationOnSyncGet(t *testing.T) { Name: "catalog1", }, Spec: &model.CatalogSpec{ - SiteId: vendor.Config.SiteInfo.SiteId, - Type: "catalog", + Type: "catalog", Properties: map[string]interface{}{ "property1": "value1", "property2": "value2", @@ -470,8 +469,7 @@ func TestFederationOnK8SHook(t *testing.T) { Name: "catalog1", }, Spec: &model.CatalogSpec{ - SiteId: vendor.Config.SiteInfo.SiteId, - Type: "catalog", + Type: "catalog", Properties: map[string]interface{}{ "property1": "value1", "property2": "value2", diff --git a/api/pkg/apis/v1alpha1/vendors/solutions-vendor.go b/api/pkg/apis/v1alpha1/vendors/solutions-vendor.go index 304181a09..83e9bea1d 100644 --- a/api/pkg/apis/v1alpha1/vendors/solutions-vendor.go +++ b/api/pkg/apis/v1alpha1/vendors/solutions-vendor.go @@ -66,7 +66,13 @@ func (o *SolutionsVendor) GetEndpoints() []v1alpha2.Endpoint { Route: route, Version: o.Version, Handler: o.onSolutions, - Parameters: []string{"name?"}, + Parameters: []string{"name", "version?"}, + }, + { + Methods: []string{fasthttp.MethodGet}, + Route: route, + Version: o.Version, + Handler: o.onSolutionsList, }, } } @@ -81,23 +87,23 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR if !exist { namespace = constants.DefaultScope } + version := request.Parameters["__version"] + rootResource := request.Parameters["__name"] + id := rootResource + "-" + version + switch request.Method { case fasthttp.MethodGet: ctx, span := observability.StartSpan("onSolutions-GET", pCtx, nil) - id := request.Parameters["__name"] + var err error var state interface{} - isArray := false - if id == "" { - // Change namespace back to empty to indicate ListSpec need to query all namespaces - if !exist { - namespace = "" - } - state, err = c.SolutionsManager.ListState(ctx, namespace) - isArray = true + + if version == "" || version == "latest" { + state, err = c.SolutionsManager.GetLatestState(ctx, id, namespace) } else { state, err = c.SolutionsManager.GetState(ctx, id, namespace) } + if err != nil { uLog.Infof("V (Solutions): onSolutions failed - %s, traceId: %s", err.Error(), span.SpanContext().TraceID().String()) return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{ @@ -105,7 +111,7 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR Body: []byte(err.Error()), }) } - jData, _ := utils.FormatObject(state, isArray, request.Parameters["path"], request.Parameters["doc-type"]) + jData, _ := utils.FormatObject(state, false, request.Parameters["path"], request.Parameters["doc-type"]) resp := observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{ State: v1alpha2.OK, Body: jData, @@ -117,7 +123,15 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR return resp case fasthttp.MethodPost: ctx, span := observability.StartSpan("onSolutions-POST", pCtx, nil) - id := request.Parameters["__name"] + + if version == "" || version == "latest" { + uLog.Infof("V (Solutions): onSolutions failed - version is required for POST, traceId: %s", span.SpanContext().TraceID().String()) + return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{ + State: v1alpha2.InternalError, + Body: []byte("version is required for POST"), + }) + } + embed_type := request.Parameters["embed-type"] embed_component := request.Parameters["embed-component"] embed_property := request.Parameters["embed-property"] @@ -141,6 +155,8 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR }, }, }, + Version: version, + RootResource: rootResource, }, } } else { @@ -155,6 +171,12 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR if solution.ObjectMeta.Name == "" { solution.ObjectMeta.Name = id } + if solution.Spec.Version == "" && version != "" { + solution.Spec.Version = version + } + if solution.Spec.RootResource == "" && rootResource != "" { + solution.Spec.RootResource = rootResource + } } err := c.SolutionsManager.UpsertState(ctx, id, solution) if err != nil { @@ -191,7 +213,7 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR }) case fasthttp.MethodDelete: ctx, span := observability.StartSpan("onSolutions-DELETE", pCtx, nil) - id := request.Parameters["__name"] + id := rootResource + ":" + version err := c.SolutionsManager.DeleteState(ctx, id, namespace) if err != nil { uLog.Infof("V (Solutions): onSolutions failed - %s, traceId: %s", err.Error(), span.SpanContext().TraceID().String()) @@ -213,3 +235,53 @@ func (c *SolutionsVendor) onSolutions(request v1alpha2.COARequest) v1alpha2.COAR observ_utils.UpdateSpanStatusFromCOAResponse(span, resp) return resp } + +func (c *SolutionsVendor) onSolutionsList(request v1alpha2.COARequest) v1alpha2.COAResponse { + pCtx, span := observability.StartSpan("Solutions Vendor", request.Context, &map[string]string{ + "method": "onSolutions", + }) + defer span.End() + uLog.Infof("V (Solutions): onSolutionsList, method: %s, traceId: %s", request.Method, span.SpanContext().TraceID().String()) + namespace, exist := request.Parameters["namespace"] + if !exist { + namespace = "default" + } + switch request.Method { + case fasthttp.MethodGet: + ctx, span := observability.StartSpan("onSolutions-GET", pCtx, nil) + + var err error + var state interface{} + // Change namespace back to empty to indicate ListSpec need to query all namespaces + if !exist { + namespace = "" + } + state, err = c.SolutionsManager.ListState(ctx, namespace) + + if err != nil { + uLog.Infof("V (Solutions): onSolutionsList failed - %s, traceId: %s", err.Error(), span.SpanContext().TraceID().String()) + return observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{ + State: v1alpha2.InternalError, + Body: []byte(err.Error()), + }) + } + jData, _ := utils.FormatObject(state, true, request.Parameters["path"], request.Parameters["doc-type"]) + resp := observ_utils.CloseSpanWithCOAResponse(span, v1alpha2.COAResponse{ + State: v1alpha2.OK, + Body: jData, + ContentType: "application/json", + }) + if request.Parameters["doc-type"] == "yaml" { + resp.ContentType = "application/text" + } + return resp + } + + resp := v1alpha2.COAResponse{ + State: v1alpha2.MethodNotAllowed, + Body: []byte("{\"result\":\"405 - method not allowed\"}"), + ContentType: "application/json", + } + observ_utils.UpdateSpanStatusFromCOAResponse(span, resp) + return resp +} diff --git a/api/pkg/apis/v1alpha1/vendors/targets-vendor.go b/api/pkg/apis/v1alpha1/vendors/targets-vendor.go index 1901cfca8..0ec08ccdd 100644 --- a/api/pkg/apis/v1alpha1/vendors/targets-vendor.go +++ b/api/pkg/apis/v1alpha1/vendors/targets-vendor.go @@ -69,7 +69,7 @@ func (o *TargetsVendor) GetEndpoints() []v1alpha2.Endpoint { Route: route + "/registry", Version: o.Version, Handler: o.onRegistry, - Parameters: []string{"name?"}, + Parameters: []string{"name", "version?"}, }, { Methods: []string{fasthttp.MethodPost}, @@ -82,7 +82,7 @@ func (o *TargetsVendor) GetEndpoints() []v1alpha2.Endpoint { Route: route + "/ping", Version: o.Version, Handler: o.onHeartBeat, - Parameters: []string{"name"}, + Parameters: []string{"name", "version"}, }, { Methods: []string{fasthttp.MethodPut}, diff --git a/api/pkg/apis/v1alpha1/vendors/visualization-vendor.go b/api/pkg/apis/v1alpha1/vendors/visualization-vendor.go index b10bf5f59..b3624b652 100644 --- a/api/pkg/apis/v1alpha1/vendors/visualization-vendor.go +++ b/api/pkg/apis/v1alpha1/vendors/visualization-vendor.go @@ -199,8 +199,7 @@ func mergeCatalogs(existingCatalog, newCatalog model.CatalogState) (model.Catalo func convertVisualizationPacketToCatalog(site string, packet model.Packet) (model.CatalogState, error) { catalog := model.CatalogState{ Spec: &model.CatalogSpec{ - SiteId: site, - Type: "topology", + Type: "topology", Properties: map[string]interface{}{ packet.From: map[string]model.Packet{ packet.To: packet, diff --git a/api/pkg/apis/v1alpha1/vendors/visualization-vendor_test.go b/api/pkg/apis/v1alpha1/vendors/visualization-vendor_test.go index 75f93d971..76984ee59 100644 --- a/api/pkg/apis/v1alpha1/vendors/visualization-vendor_test.go +++ b/api/pkg/apis/v1alpha1/vendors/visualization-vendor_test.go @@ -112,7 +112,6 @@ func TestConvertVisualizationPacketToCatalog(t *testing.T) { DataType: "bytes", }) assert.Nil(t, err) - assert.Equal(t, "fake-site", catalog.Spec.SiteId) assert.Equal(t, "topology", catalog.Spec.Type) v, ok := catalog.Spec.Properties["from-1"].(map[string]model.Packet) @@ -131,7 +130,6 @@ func TestConvertVisualizationPacketToCatalogNoData(t *testing.T) { To: "to-1", }) assert.Nil(t, err) - assert.Equal(t, "fake-site", catalog.Spec.SiteId) assert.Equal(t, "topology", catalog.Spec.Type) v, ok := catalog.Spec.Properties["from-1"].(map[string]model.Packet) @@ -150,7 +148,6 @@ func TestMergeCatalogsSameKey(t *testing.T) { DataType: "bytes", }) assert.Nil(t, err) - assert.Equal(t, "fake-site", catalog1.Spec.SiteId) assert.Equal(t, "topology", catalog1.Spec.Type) catalog2, err := convertVisualizationPacketToCatalog("fake-site", model.Packet{ Solution: "solution-1", @@ -162,7 +159,6 @@ func TestMergeCatalogsSameKey(t *testing.T) { DataType: "bytes", }) assert.Nil(t, err) - assert.Equal(t, "fake-site", catalog2.Spec.SiteId) assert.Equal(t, "topology", catalog2.Spec.Type) mergedCatalog, err := mergeCatalogs(catalog1, catalog2) @@ -190,7 +186,6 @@ func TestMergeCatalogsDifferentKey(t *testing.T) { DataType: "bytes", }) assert.Nil(t, err) - assert.Equal(t, "fake-site", catalog1.Spec.SiteId) assert.Equal(t, "topology", catalog1.Spec.Type) catalog2, err := convertVisualizationPacketToCatalog("fake-site", model.Packet{ Solution: "solution-1", @@ -202,7 +197,6 @@ func TestMergeCatalogsDifferentKey(t *testing.T) { DataType: "bytes", }) assert.Nil(t, err) - assert.Equal(t, "fake-site", catalog2.Spec.SiteId) assert.Equal(t, "topology", catalog2.Spec.Type) mergedCatalog, err := mergeCatalogs(catalog1, catalog2) diff --git a/api/symphony-script-over-mqtt.json b/api/symphony-script-over-mqtt.json index 28bbdcd6a..a6f34ab71 100644 --- a/api/symphony-script-over-mqtt.json +++ b/api/symphony-script-over-mqtt.json @@ -26,7 +26,7 @@ "providers.target": "script", "providers.state": "mem-state", "providers.config": "mock-config", - "providers.secret": "mock-secret" + "providers.secret": "mock-secret" }, "providers": { "script": { diff --git a/coa/pkg/apis/v1alpha2/bindings/http/jwt.go b/coa/pkg/apis/v1alpha2/bindings/http/jwt.go index 89097b9ec..ac2ad9e1a 100644 --- a/coa/pkg/apis/v1alpha2/bindings/http/jwt.go +++ b/coa/pkg/apis/v1alpha2/bindings/http/jwt.go @@ -89,19 +89,25 @@ func (j JWT) JWT(next fasthttp.RequestHandler) fasthttp.RequestHandler { } tokenStr := j.readAuthHeader(ctx) if tokenStr == "" { + log.Errorf("JWT: Token is empty.\n") ctx.Response.SetStatusCode(fasthttp.StatusForbidden) } else { if j.AuthServer == AuthServerKuberenetes { + log.Debugf("JWT: Validating token with k8s.\n") err := j.validateServiceAccountToken(ctx, tokenStr) if err != nil { + log.Errorf("JWT: Validate token with k8s failed. %s\n", err.Error()) ctx.Response.SetStatusCode(fasthttp.StatusForbidden) return } next(ctx) } else { + log.Debugf("JWT: Validating token with username plus pwd.\n") _, roles, err := j.validateToken(tokenStr) if err != nil { + log.Error("JWT: Validate token with user creds failed. %s\n", err.Error()) ctx.Response.SetStatusCode(fasthttp.StatusForbidden) + return } else { if j.EnableRBAC { path := string(ctx.Path()) @@ -207,6 +213,7 @@ func (j *JWT) validateToken(tokenStr string) (map[string]interface{}, []string, func (j *JWT) validateServiceAccountToken(ctx *fasthttp.RequestCtx, tokenStr string) error { clientset, err := getKubernetesClient() if err != nil { + log.Errorf("JWT: Could not initialize Kubernetes client.\n") return v1alpha2.NewCOAError(err, "Could not initialize Kubernetes client", v1alpha2.InternalError) } tokenReview := &v1.TokenReview{ @@ -217,11 +224,14 @@ func (j *JWT) validateServiceAccountToken(ctx *fasthttp.RequestCtx, tokenStr str }, }, } + result, err := clientset.AuthenticationV1().TokenReviews().Create(ctx, tokenReview, metav1.CreateOptions{}) if err != nil { + log.Errorf("JWT: Token review using kubernetes api server failed. %s\n", err.Error()) return v1alpha2.NewCOAError(err, "Token review using kubernetes api server failed.", v1alpha2.InternalError) } if !result.Status.Authenticated { + log.Errorf("JWT: Validate token with k8s failed. K8s returned not authenticated.\n") return v1alpha2.NewCOAError(nil, "Authentication failed.", v1alpha2.Unauthorized) } else { apiUsername, err := getApiServiceAccountUsername() @@ -233,6 +243,7 @@ func (j *JWT) validateServiceAccountToken(ctx *fasthttp.RequestCtx, tokenStr str return err } if result.Status.User.Username != apiUsername && result.Status.User.Username != controllerUsername { + log.Errorf("JWT: Validate token with k8s failed. K8s returned invalid username, %s\n", result.Status.User.Username) return v1alpha2.NewCOAError(nil, "Authentication failed.", v1alpha2.Unauthorized) } } diff --git a/coa/pkg/apis/v1alpha2/providers/states/httpstate/httpstate.go b/coa/pkg/apis/v1alpha2/providers/states/httpstate/httpstate.go index 942f9b591..c7f2b20db 100644 --- a/coa/pkg/apis/v1alpha2/providers/states/httpstate/httpstate.go +++ b/coa/pkg/apis/v1alpha2/providers/states/httpstate/httpstate.go @@ -279,6 +279,10 @@ func (s *HttpStateProvider) Get(ctx context.Context, request states.GetRequest) }, nil } +func (s *HttpStateProvider) GetLatest(ctx context.Context, request states.GetRequest) (states.StateEntry, error) { + return states.StateEntry{}, v1alpha2.NewCOAError(nil, "Http state store get latest is not implemented", v1alpha2.NotImplemented) +} + func toHttpStateProviderConfig(config providers.IProviderConfig) (HttpStateProviderConfig, error) { ret := HttpStateProviderConfig{} data, err := json.Marshal(config) diff --git a/coa/pkg/apis/v1alpha2/providers/states/memorystate/memorystate.go b/coa/pkg/apis/v1alpha2/providers/states/memorystate/memorystate.go index 3cfa46f94..aadd19fcb 100644 --- a/coa/pkg/apis/v1alpha2/providers/states/memorystate/memorystate.go +++ b/coa/pkg/apis/v1alpha2/providers/states/memorystate/memorystate.go @@ -430,6 +430,10 @@ func (s *MemoryStateProvider) Get(ctx context.Context, request states.GetRequest return states.StateEntry{}, err } +func (s *MemoryStateProvider) GetLatest(ctx context.Context, request states.GetRequest) (states.StateEntry, error) { + return states.StateEntry{}, v1alpha2.NewCOAError(nil, "Memory state store get latest is not implemented", v1alpha2.NotImplemented) +} + func toMemoryStateProviderConfig(config providers.IProviderConfig) (MemoryStateProviderConfig, error) { ret := MemoryStateProviderConfig{} data, err := json.Marshal(config) diff --git a/coa/pkg/apis/v1alpha2/providers/states/states.go b/coa/pkg/apis/v1alpha2/providers/states/states.go index e4b9d418c..0e202b7a5 100644 --- a/coa/pkg/apis/v1alpha2/providers/states/states.go +++ b/coa/pkg/apis/v1alpha2/providers/states/states.go @@ -25,6 +25,7 @@ type IStateProvider interface { Upsert(context.Context, UpsertRequest) (string, error) Delete(context.Context, DeleteRequest) error Get(context.Context, GetRequest) (StateEntry, error) + GetLatest(context.Context, GetRequest) (StateEntry, error) List(context.Context, ListRequest) ([]StateEntry, string, error) SetContext(context *contexts.ManagerContext) } diff --git a/docs/samples/approval/logicapp/instance-catalog.yaml b/docs/samples/approval/logicapp/instance-catalog.yaml index d0f8bf856..d5dd70349 100644 --- a/docs/samples/approval/logicapp/instance-catalog.yaml +++ b/docs/samples/approval/logicapp/instance-catalog.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: gated-prometheus-instance -spec: - siteId: hq +spec: type: instance properties: spec: diff --git a/docs/samples/approval/script/instance-catalog.yaml b/docs/samples/approval/script/instance-catalog.yaml index d0f8bf856..d5dd70349 100644 --- a/docs/samples/approval/script/instance-catalog.yaml +++ b/docs/samples/approval/script/instance-catalog.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: gated-prometheus-instance -spec: - siteId: hq +spec: type: instance properties: spec: diff --git a/docs/samples/multisite/catalog-catalog.yaml b/docs/samples/multisite/catalog-catalog.yaml index 472f02307..e6bf85774 100644 --- a/docs/samples/multisite/catalog-catalog.yaml +++ b/docs/samples/multisite/catalog-catalog.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: site-catalog -spec: - siteId: hq +spec: type: catalog properties: metadata: diff --git a/docs/samples/multisite/instance-catalog.yaml b/docs/samples/multisite/instance-catalog.yaml index 1e21206a2..6d36603e6 100644 --- a/docs/samples/multisite/instance-catalog.yaml +++ b/docs/samples/multisite/instance-catalog.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: site-instance -spec: - siteId: hq +spec: type: instance properties: spec: diff --git a/docs/samples/multisite/solution-catalog.yaml b/docs/samples/multisite/solution-catalog.yaml index 940638c6f..d38d3443b 100644 --- a/docs/samples/multisite/solution-catalog.yaml +++ b/docs/samples/multisite/solution-catalog.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: site-app -spec: - siteId: hq +spec: type: solution properties: spec: diff --git a/docs/samples/multisite/target-catalog.yaml b/docs/samples/multisite/target-catalog.yaml index 5a453d0f9..5a2032cd1 100644 --- a/docs/samples/multisite/target-catalog.yaml +++ b/docs/samples/multisite/target-catalog.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: site-k8s-target -spec: - siteId: hq +spec: type: target properties: spec: diff --git a/docs/samples/opera/app/types.d.ts b/docs/samples/opera/app/types.d.ts index 2f9963e90..eba1dbd24 100644 --- a/docs/samples/opera/app/types.d.ts +++ b/docs/samples/opera/app/types.d.ts @@ -46,7 +46,7 @@ export interface CampaignSpec { } export interface ObjectRef { - siteId: string; + siteId: string; name: string; group: string; version: string; @@ -58,7 +58,6 @@ export interface ObjectRef { } export interface CatalogSpec { - siteId: string; name: string; type: string; properties: Record; diff --git a/docs/samples/opera/components/editors/CatalogEditor.tsx b/docs/samples/opera/components/editors/CatalogEditor.tsx index e3e87349d..c1f1540a9 100644 --- a/docs/samples/opera/components/editors/CatalogEditor.tsx +++ b/docs/samples/opera/components/editors/CatalogEditor.tsx @@ -42,7 +42,6 @@ function CatalogEditor(props: CatalogEditorProps) { const formData = new FormData(event.currentTarget); const data = Object.fromEntries(formData.entries()); const catalog = { - siteId : process.env.SYMPHONY_SITE, name: data.name, type: "config" }; diff --git a/docs/samples/universe-data/chemical-factory-2/catalogs/assets.yaml b/docs/samples/universe-data/chemical-factory-2/catalogs/assets.yaml index 6fe3e1761..5b0ecf5aa 100644 --- a/docs/samples/universe-data/chemical-factory-2/catalogs/assets.yaml +++ b/docs/samples/universe-data/chemical-factory-2/catalogs/assets.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: hq -spec: - siteId: hq +spec: type: asset properties: name: HQ @@ -21,8 +20,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: infrastructure -spec: - siteId: hq +spec: type: asset properties: name: "Infrastructure" @@ -32,8 +30,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: l3 -spec: - siteId: hq +spec: type: asset properties: name: "Level 3" @@ -43,8 +40,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: l4 -spec: - siteId: hq +spec: type: asset properties: name: "Level 4" @@ -54,8 +50,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: use-cases -spec: - siteId: hq +spec: type: asset properties: name: "Use Cases" @@ -65,8 +60,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: csad -spec: - siteId: hq +spec: type: asset properties: name: "CSAD" @@ -76,8 +70,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: site -spec: - siteId: hq +spec: type: asset properties: name: "Site" @@ -87,8 +80,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: line-a -spec: - siteId: hq +spec: type: asset properties: name: "Line A" @@ -98,8 +90,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: line-b -spec: - siteId: hq +spec: type: asset properties: name: "Line B" diff --git a/docs/samples/universe-data/chemical-factory-2/catalogs/configurations.yaml b/docs/samples/universe-data/chemical-factory-2/catalogs/configurations.yaml index 7117cff92..69c4313f5 100644 --- a/docs/samples/universe-data/chemical-factory-2/catalogs/configurations.yaml +++ b/docs/samples/universe-data/chemical-factory-2/catalogs/configurations.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: global-config -spec: - siteId: hq +spec: type: config name: global-config metadata: @@ -17,8 +16,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: l3-config -spec: - siteId: hq +spec: type: config metadata: asset: l3 @@ -31,8 +29,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: l4-config -spec: - siteId: hq +spec: type: config metadata: asset: l4 @@ -45,8 +42,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: csad-config -spec: - siteId: hq +spec: type: config parentName: global-config metadata: @@ -59,8 +55,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: site-config -spec: - siteId: hq +spec: type: config metadata: asset: site @@ -74,8 +69,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: line-a-config -spec: - siteId: hq +spec: type: config metadata: asset: line-a @@ -88,8 +82,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: line-b-config -spec: - siteId: hq +spec: type: config metadata: asset: line-b diff --git a/docs/samples/universe-data/chemical-factory/configurations-with-schema.yaml b/docs/samples/universe-data/chemical-factory/configurations-with-schema.yaml index 1a855943a..7ff663618 100644 --- a/docs/samples/universe-data/chemical-factory/configurations-with-schema.yaml +++ b/docs/samples/universe-data/chemical-factory/configurations-with-schema.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: sample-config -spec: - siteId: hq +spec: type: config metadata: schema: sample-schema diff --git a/docs/samples/universe-data/chemical-factory/configurations.yaml b/docs/samples/universe-data/chemical-factory/configurations.yaml index a74e897b2..f1944e07c 100644 --- a/docs/samples/universe-data/chemical-factory/configurations.yaml +++ b/docs/samples/universe-data/chemical-factory/configurations.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: influx-db-config -spec: - siteId: hq +spec: type: config name: influx-db-config properties: @@ -19,8 +18,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: grafana-config -spec: - siteId: hq +spec: type: config properties: host: localhost @@ -33,8 +31,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: e4k-config -spec: - siteId: hq +spec: type: config properties: host: localhost @@ -47,8 +44,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: bluefin-config -spec: - siteId: hq +spec: type: config properties: host: localhost @@ -61,8 +57,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: ai-config -spec: - siteId: hq +spec: type: config metadata: asset: hq @@ -75,8 +70,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: ai-config-site -spec: - siteId: hq +spec: type: config parentName: ai-config metadata: @@ -89,8 +83,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: ai-config-line -spec: - siteId: hq +spec: type: config metadata: asset: line-1 @@ -102,8 +95,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: combined -spec: - siteId: hq +spec: type: config properties: foo: bar @@ -119,8 +111,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: combined-1 -spec: - siteId: hq +spec: type: config properties: foo: .foo @@ -130,8 +121,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: combined-2 -spec: - siteId: hq +spec: type: config properties: foo: bar2 @@ -141,8 +131,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: external -spec: - siteId: hq +spec: type: config properties: foo: far diff --git a/docs/samples/universe-data/chemical-factory/manifests.yaml b/docs/samples/universe-data/chemical-factory/manifests.yaml index 49f2e5337..9565dbf6c 100644 --- a/docs/samples/universe-data/chemical-factory/manifests.yaml +++ b/docs/samples/universe-data/chemical-factory/manifests.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: site-app-config -spec: - siteId: hq +spec: type: config name: site-app-config properties: @@ -14,8 +13,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: line-app-config -spec: - siteId: hq +spec: type: config properties: cat: leory @@ -25,8 +23,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: site-app -spec: - siteId: hq +spec: type: solution properties: spec: @@ -50,8 +47,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: line-app -spec: - siteId: hq +spec: type: solution properties: spec: @@ -137,8 +133,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: smart-fridge -spec: - siteId: hq +spec: type: solution properties: spec: @@ -164,8 +159,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: site-instance -spec: - siteId: hq +spec: type: instance properties: spec: diff --git a/docs/samples/universe-data/chemical-factory/schemas.yaml b/docs/samples/universe-data/chemical-factory/schemas.yaml index 611a8d200..b86fa70ab 100644 --- a/docs/samples/universe-data/chemical-factory/schemas.yaml +++ b/docs/samples/universe-data/chemical-factory/schemas.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: sample-schema -spec: - siteId: hq +spec: type: schema properties: spec: diff --git a/docs/samples/universe-data/chemical-factory/sites.yaml b/docs/samples/universe-data/chemical-factory/sites.yaml index 6c46075e0..9ef9dea57 100644 --- a/docs/samples/universe-data/chemical-factory/sites.yaml +++ b/docs/samples/universe-data/chemical-factory/sites.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: hq -spec: - siteId: hq +spec: type: asset properties: name: HQ @@ -21,8 +20,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: tokyo -spec: - siteId: tokyo +spec: type: asset properties: name: "東京" @@ -40,8 +38,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: new-york -spec: - siteId: new-york +spec: type: asset properties: name: "New York" @@ -59,8 +56,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: munchen -spec: - siteId: munchen +spec: type: asset properties: name: "München" @@ -78,8 +74,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: hq-adr -spec: - siteId: hq +spec: type: asset properties: name: "HQ Azure Device Registry" @@ -99,8 +94,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: hq-arc-1 -spec: - siteId: hq +spec: type: asset properties: name: "HQ Azure Arc Cluster 1" @@ -120,8 +114,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: hq-arc-2 -spec: - siteId: hq +spec: type: asset properties: name: "HQ Azure Arc Cluster 2" @@ -141,8 +134,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: hq-doe-site -spec: - siteId: hq +spec: type: asset properties: name: "HQ DOE Site" @@ -162,8 +154,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: hq-iot-hub -spec: - siteId: hq +spec: type: asset properties: name: "HQ IoT Hub Tenant" @@ -183,8 +174,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: area-1 -spec: - siteId: hq +spec: type: asset properties: name: "Area 1" @@ -194,8 +184,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: area-2 -spec: - siteId: hq +spec: type: asset properties: name: "Area 2" @@ -205,8 +194,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: line-1 -spec: - siteId: hq +spec: type: asset properties: name: "Production Line 1" @@ -216,8 +204,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: line-2 -spec: - siteId: hq +spec: type: asset properties: name: "Production Line 2" diff --git a/docs/symphony-book/api/symphony-api-openapi.yaml b/docs/symphony-book/api/symphony-api-openapi.yaml index 6e7ad0d5d..7a1821827 100644 --- a/docs/symphony-book/api/symphony-api-openapi.yaml +++ b/docs/symphony-book/api/symphony-api-openapi.yaml @@ -411,7 +411,6 @@ paths: schema: type: object example: - siteId: hq name: '{{CATALOG_NAME}}' type: asset properties: @@ -502,7 +501,6 @@ paths: schema: type: object example: - siteId: hq name: '{{CATALOG_NAME}}' type: asset properties: @@ -525,7 +523,6 @@ paths: schema: type: object example: - siteId: hq name: '{{CATALOG_NAME}}-2' type: asset properties: @@ -1562,7 +1559,6 @@ paths: schema: type: object example: - siteId: hq name: '{{CATALOG_NAME}}' type: asset properties: diff --git a/docs/symphony-book/build_deployment/build.md b/docs/symphony-book/build_deployment/build.md index 10dfa07a9..6b7f34999 100644 --- a/docs/symphony-book/build_deployment/build.md +++ b/docs/symphony-book/build_deployment/build.md @@ -165,7 +165,7 @@ docker push ghcr.io/eclipse-symphony/symphony-k8s:latest ```bash cd k8s mage helmTemplate -# Generated startup yaml will be updated in ../packages/helm/symphony/templates/symphony.yaml. +# Generated startup yaml will be updated in ../packages/helm/symphony/templates/symphony-core/symphonyk8s.yaml. ``` > **IMPORTANT**: With current Kustomize, empty `creationTimestamp` properties are inserted into the generated artifacts somehow, causing Helm chart to fail. You'll need to manually remove all occurrence of `creationTimestamp` properties with `null` or `"null"` from the artifacts, until a proper solution is found. diff --git a/docs/symphony-book/concepts/unified-object-model/catalog.md b/docs/symphony-book/concepts/unified-object-model/catalog.md index 146155ad5..03f038677 100644 --- a/docs/symphony-book/concepts/unified-object-model/catalog.md +++ b/docs/symphony-book/concepts/unified-object-model/catalog.md @@ -10,7 +10,6 @@ kind: Catalog metadata: name: hq spec: - siteId: hq type: asset properties: name: HQ @@ -35,7 +34,6 @@ kind: Catalog metadata: name: edges spec: - siteId: hq type: edge properties: node1: node2 @@ -52,7 +50,6 @@ kind: Catalog metadata: name: app-config spec: - siteId: hq type: config parentName: global-config metadata: diff --git a/docs/symphony-book/configuration-management/define-configurations.md b/docs/symphony-book/configuration-management/define-configurations.md index b73710b17..b464bd253 100644 --- a/docs/symphony-book/configuration-management/define-configurations.md +++ b/docs/symphony-book/configuration-management/define-configurations.md @@ -7,8 +7,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: robot-config -spec: - siteId: hq +spec: type: config properties: name: my-robot diff --git a/go.work.bk b/go.work.bk index 531f7f33a..41ff404f5 100644 --- a/go.work.bk +++ b/go.work.bk @@ -1,7 +1,10 @@ go 1.20 use ./api + use ./coa + use ./k8s + use ./cli use ./test/integration \ No newline at end of file diff --git a/go.work.sum b/go.work.sum index f067fe84d..883c7acd4 100644 --- a/go.work.sum +++ b/go.work.sum @@ -368,4 +368,4 @@ k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAE k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/kms v0.26.0/go.mod h1:ReC1IEGuxgfN+PDCIpR6w8+XMmDE7uJhxcCwMZFdIYc= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33/go.mod h1:soWkSNf2tZC7aMibXEqVhCd73GOY5fJikn8qbdzemB0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33/go.mod h1:soWkSNf2tZC7aMibXEqVhCd73GOY5fJikn8qbdzemB0= \ No newline at end of file diff --git a/k8s/PROJECT b/k8s/PROJECT index 03909b895..2270c0fb1 100644 --- a/k8s/PROJECT +++ b/k8s/PROJECT @@ -152,4 +152,49 @@ resources: defaulting: true validation: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: symphony + group: solution + kind: InstanceContainer + path: gopls-workspace/apis/solution/v1 + version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: symphony + group: solution + kind: SolutionContainer + path: gopls-workspace/apis/solution/v1 + version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: symphony + group: fabric + kind: TargetContainer + path: gopls-workspace/apis/fabric/v1 + version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: symphony + group: federation + kind: CatalogContainer + path: gopls-workspace/apis/federation/v1 + version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: symphony + group: workflow + kind: CampaignContainer + path: gopls-workspace/apis/workflow/v1 + version: v1 version: "3" diff --git a/k8s/apis/fabric/v1/targetcontainer_types.go b/k8s/apis/fabric/v1/targetcontainer_types.go new file mode 100644 index 000000000..331c2ceb7 --- /dev/null +++ b/k8s/apis/fabric/v1/targetcontainer_types.go @@ -0,0 +1,45 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package v1 + +import ( + apimodel "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// TargetContainerStatus defines the observed state of Target +type TargetContainerStatus struct { + // Important: Run "make" to regenerate code after modifying this file + Properties map[string]string `json:"properties,omitempty"` + ProvisioningStatus apimodel.ProvisioningStatus `json:"provisioningStatus"` + LastModified metav1.Time `json:"lastModified,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.properties.status` +// Target is the Schema for the targets API +type TargetContainer struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec k8smodel.TargetContainerSpec `json:"spec,omitempty"` + Status TargetContainerStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// TargetList contains a list of Target +type TargetContainerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TargetContainer `json:"items"` +} + +func init() { + SchemeBuilder.Register(&TargetContainer{}, &TargetContainerList{}) +} diff --git a/k8s/apis/fabric/v1/zz_generated.deepcopy.go b/k8s/apis/fabric/v1/zz_generated.deepcopy.go index 71072473a..7c86e8c2b 100644 --- a/k8s/apis/fabric/v1/zz_generated.deepcopy.go +++ b/k8s/apis/fabric/v1/zz_generated.deepcopy.go @@ -123,6 +123,89 @@ func (in *Target) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetContainer) DeepCopyInto(out *TargetContainer) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetContainer. +func (in *TargetContainer) DeepCopy() *TargetContainer { + if in == nil { + return nil + } + out := new(TargetContainer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TargetContainer) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetContainerList) DeepCopyInto(out *TargetContainerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TargetContainer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetContainerList. +func (in *TargetContainerList) DeepCopy() *TargetContainerList { + if in == nil { + return nil + } + out := new(TargetContainerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TargetContainerList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetContainerStatus) DeepCopyInto(out *TargetContainerStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetContainerStatus. +func (in *TargetContainerStatus) DeepCopy() *TargetContainerStatus { + if in == nil { + return nil + } + out := new(TargetContainerStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TargetList) DeepCopyInto(out *TargetList) { *out = *in @@ -154,3 +237,27 @@ func (in *TargetList) DeepCopyObject() runtime.Object { } return nil } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetStatus) DeepCopyInto(out *TargetStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetStatus. +func (in *TargetStatus) DeepCopy() *TargetStatus { + if in == nil { + return nil + } + out := new(TargetStatus) + in.DeepCopyInto(out) + return out +} diff --git a/k8s/apis/federation/v1/catalog_types.go b/k8s/apis/federation/v1/catalog_types.go index 4bd445713..35f572a01 100644 --- a/k8s/apis/federation/v1/catalog_types.go +++ b/k8s/apis/federation/v1/catalog_types.go @@ -12,10 +12,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type CatalogStatus struct { - Properties map[string]string `json:"properties"` -} - // +kubebuilder:object:root=true // +kubebuilder:subresource:status // Catalog is the Schema for the catalogs API @@ -23,8 +19,7 @@ type Catalog struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec k8smodel.CatalogSpec `json:"spec,omitempty"` - Status CatalogStatus `json:"status,omitempty"` + Spec k8smodel.CatalogSpec `json:"spec,omitempty"` } // +kubebuilder:object:root=true diff --git a/k8s/apis/federation/v1/catalogcontainer_types.go b/k8s/apis/federation/v1/catalogcontainer_types.go new file mode 100644 index 000000000..92fb5cf85 --- /dev/null +++ b/k8s/apis/federation/v1/catalogcontainer_types.go @@ -0,0 +1,42 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package v1 + +import ( + apimodel "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type CatalogContainerStatus struct { + Properties map[string]string `json:"properties"` + ProvisioningStatus apimodel.ProvisioningStatus `json:"provisioningStatus"` + LastModified metav1.Time `json:"lastModified,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// CatalogContainer is the Schema for the catalogs API +type CatalogContainer struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec k8smodel.CatalogContainerSpec `json:"spec,omitempty"` + Status CatalogContainerStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// CatalogContainerList contains a list of Catalog +type CatalogContainerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CatalogContainer `json:"items"` +} + +func init() { + SchemeBuilder.Register(&CatalogContainer{}, &CatalogContainerList{}) +} diff --git a/k8s/apis/federation/v1/zz_generated.deepcopy.go b/k8s/apis/federation/v1/zz_generated.deepcopy.go index d15ffc6d2..dedefd170 100644 --- a/k8s/apis/federation/v1/zz_generated.deepcopy.go +++ b/k8s/apis/federation/v1/zz_generated.deepcopy.go @@ -21,7 +21,6 @@ func (in *Catalog) DeepCopyInto(out *Catalog) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Catalog. @@ -43,31 +42,58 @@ func (in *Catalog) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CatalogList) DeepCopyInto(out *CatalogList) { +func (in *CatalogContainer) DeepCopyInto(out *CatalogContainer) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogContainer. +func (in *CatalogContainer) DeepCopy() *CatalogContainer { + if in == nil { + return nil + } + out := new(CatalogContainer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CatalogContainer) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CatalogContainerList) DeepCopyInto(out *CatalogContainerList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]Catalog, len(*in)) + *out = make([]CatalogContainer, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogList. -func (in *CatalogList) DeepCopy() *CatalogList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogContainerList. +func (in *CatalogContainerList) DeepCopy() *CatalogContainerList { if in == nil { return nil } - out := new(CatalogList) + out := new(CatalogContainerList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CatalogList) DeepCopyObject() runtime.Object { +func (in *CatalogContainerList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -75,7 +101,7 @@ func (in *CatalogList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CatalogStatus) DeepCopyInto(out *CatalogStatus) { +func (in *CatalogContainerStatus) DeepCopyInto(out *CatalogContainerStatus) { *out = *in if in.Properties != nil { in, out := &in.Properties, &out.Properties @@ -84,18 +110,52 @@ func (in *CatalogStatus) DeepCopyInto(out *CatalogStatus) { (*out)[key] = val } } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogContainerStatus. +func (in *CatalogContainerStatus) DeepCopy() *CatalogContainerStatus { + if in == nil { + return nil + } + out := new(CatalogContainerStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CatalogList) DeepCopyInto(out *CatalogList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Catalog, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogStatus. -func (in *CatalogStatus) DeepCopy() *CatalogStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogList. +func (in *CatalogList) DeepCopy() *CatalogList { if in == nil { return nil } - out := new(CatalogStatus) + out := new(CatalogList) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CatalogList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Site) DeepCopyInto(out *Site) { *out = *in diff --git a/k8s/apis/model/v1/common_types.go b/k8s/apis/model/v1/common_types.go index f9d6cbe87..71463d112 100644 --- a/k8s/apis/model/v1/common_types.go +++ b/k8s/apis/model/v1/common_types.go @@ -99,13 +99,29 @@ type InstanceSpec struct { ReconciliationPolicy *ReconciliationPolicySpec `json:"reconciliationPolicy,omitempty"` } +// +kubebuilder:object:generate=true +type TargetContainerSpec struct { + DisplayName string `json:"displayName,omitempty"` +} + +// +kubebuilder:object:generate=true +type InstanceContainerSpec struct { + DisplayName string `json:"displayName,omitempty"` +} + // +kubebuilder:object:generate=true type SolutionSpec struct { DisplayName string `json:"displayName,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` Components []ComponentSpec `json:"components,omitempty"` // Defines the version of a particular resource - Version string `json:"version,omitempty"` + Version string `json:"version,omitempty"` + RootResource string `json:"rootResource,omitempty"` +} + +// +kubebuilder:object:generate=true +type SolutionContainerSpec struct { + DisplayName string `json:"displayName,omitempty"` } // +kubebuilder:object:generate=true @@ -149,9 +165,13 @@ type CampaignSpec struct { SelfDriving bool `json:"selfDriving,omitempty"` } +// +kubebuilder:object:generate=true +type CampaignContainerSpec struct { + DisplayName string `json:"displayName,omitempty"` +} + // +kubebuilder:object:generate=true type CatalogSpec struct { - SiteId string `json:"siteId"` Type string `json:"type"` Metadata map[string]string `json:"metadata,omitempty"` // +kubebuilder:pruning:PreserveUnknownFields @@ -162,6 +182,11 @@ type CatalogSpec struct { Generation string `json:"generation,omitempty"` } +// +kubebuilder:object:generate=true +type CatalogContainerSpec struct { + DisplayName string `json:"displayName,omitempty"` +} + // +kubebuilder:object:generate=true type DeployableStatus struct { Properties map[string]string `json:"properties,omitempty"` diff --git a/k8s/apis/model/v1/zz_generated.deepcopy.go b/k8s/apis/model/v1/zz_generated.deepcopy.go index ba92cf76d..d721fff47 100644 --- a/k8s/apis/model/v1/zz_generated.deepcopy.go +++ b/k8s/apis/model/v1/zz_generated.deepcopy.go @@ -31,6 +31,21 @@ func (in *ActivationSpec) DeepCopy() *ActivationSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CampaignContainerSpec) DeepCopyInto(out *CampaignContainerSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CampaignContainerSpec. +func (in *CampaignContainerSpec) DeepCopy() *CampaignContainerSpec { + if in == nil { + return nil + } + out := new(CampaignContainerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CampaignSpec) DeepCopyInto(out *CampaignSpec) { *out = *in @@ -53,6 +68,21 @@ func (in *CampaignSpec) DeepCopy() *CampaignSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CatalogContainerSpec) DeepCopyInto(out *CatalogContainerSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogContainerSpec. +func (in *CatalogContainerSpec) DeepCopy() *CatalogContainerSpec { + if in == nil { + return nil + } + out := new(CatalogContainerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CatalogSpec) DeepCopyInto(out *CatalogSpec) { *out = *in @@ -148,6 +178,21 @@ func (in *DeployableStatus) DeepCopy() *DeployableStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstanceContainerSpec) DeepCopyInto(out *InstanceContainerSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceContainerSpec. +func (in *InstanceContainerSpec) DeepCopy() *InstanceContainerSpec { + if in == nil { + return nil + } + out := new(InstanceContainerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InstanceSpec) DeepCopyInto(out *InstanceSpec) { *out = *in @@ -265,6 +310,21 @@ func (in *SidecarSpec) DeepCopy() *SidecarSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SolutionContainerSpec) DeepCopyInto(out *SolutionContainerSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SolutionContainerSpec. +func (in *SolutionContainerSpec) DeepCopy() *SolutionContainerSpec { + if in == nil { + return nil + } + out := new(SolutionContainerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SolutionSpec) DeepCopyInto(out *SolutionSpec) { *out = *in @@ -316,6 +376,21 @@ func (in *StageSpec) DeepCopy() *StageSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetContainerSpec) DeepCopyInto(out *TargetContainerSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetContainerSpec. +func (in *TargetContainerSpec) DeepCopy() *TargetContainerSpec { + if in == nil { + return nil + } + out := new(TargetContainerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TargetSpec) DeepCopyInto(out *TargetSpec) { *out = *in diff --git a/k8s/apis/solution/v1/instancecontainer_types.go b/k8s/apis/solution/v1/instancecontainer_types.go new file mode 100644 index 000000000..3eccf6e99 --- /dev/null +++ b/k8s/apis/solution/v1/instancecontainer_types.go @@ -0,0 +1,42 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package v1 + +import ( + apimodel "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type InstanceContainerStatus struct { + Properties map[string]string `json:"properties"` + ProvisioningStatus apimodel.ProvisioningStatus `json:"provisioningStatus"` + LastModified metav1.Time `json:"lastModified,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// InstanceContainer is the Schema for the InstanceContainers API +type InstanceContainer struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec k8smodel.InstanceContainerSpec `json:"spec,omitempty"` + Status InstanceContainerStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// InstanceContainer1List contains a list of InstanceContainer +type InstanceContainerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []InstanceContainer `json:"items"` +} + +func init() { + SchemeBuilder.Register(&InstanceContainer{}, &InstanceContainerList{}) +} diff --git a/k8s/apis/solution/v1/solutioncontainer_types.go b/k8s/apis/solution/v1/solutioncontainer_types.go new file mode 100644 index 000000000..ab37d6197 --- /dev/null +++ b/k8s/apis/solution/v1/solutioncontainer_types.go @@ -0,0 +1,42 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package v1 + +import ( + apimodel "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type SolutionContainerStatus struct { + Properties map[string]string `json:"properties"` + ProvisioningStatus apimodel.ProvisioningStatus `json:"provisioningStatus"` + LastModified metav1.Time `json:"lastModified,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// SolutionContainer is the Schema for the SolutionContainer API +type SolutionContainer struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec k8smodel.SolutionContainerSpec `json:"spec,omitempty"` + Status SolutionContainerStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// SolutionContainerList contains a list of SolutionContainer +type SolutionContainerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SolutionContainer `json:"items"` +} + +func init() { + SchemeBuilder.Register(&SolutionContainer{}, &SolutionContainerList{}) +} diff --git a/k8s/apis/solution/v1/zz_generated.deepcopy.go b/k8s/apis/solution/v1/zz_generated.deepcopy.go index 2857134f2..97e579248 100644 --- a/k8s/apis/solution/v1/zz_generated.deepcopy.go +++ b/k8s/apis/solution/v1/zz_generated.deepcopy.go @@ -42,6 +42,89 @@ func (in *Instance) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstanceContainer) DeepCopyInto(out *InstanceContainer) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceContainer. +func (in *InstanceContainer) DeepCopy() *InstanceContainer { + if in == nil { + return nil + } + out := new(InstanceContainer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InstanceContainer) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstanceContainerList) DeepCopyInto(out *InstanceContainerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]InstanceContainer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceContainerList. +func (in *InstanceContainerList) DeepCopy() *InstanceContainerList { + if in == nil { + return nil + } + out := new(InstanceContainerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InstanceContainerList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstanceContainerStatus) DeepCopyInto(out *InstanceContainerStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceContainerStatus. +func (in *InstanceContainerStatus) DeepCopy() *InstanceContainerStatus { + if in == nil { + return nil + } + out := new(InstanceContainerStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InstanceList) DeepCopyInto(out *InstanceList) { *out = *in @@ -101,6 +184,89 @@ func (in *Solution) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SolutionContainer) DeepCopyInto(out *SolutionContainer) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SolutionContainer. +func (in *SolutionContainer) DeepCopy() *SolutionContainer { + if in == nil { + return nil + } + out := new(SolutionContainer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SolutionContainer) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SolutionContainerList) DeepCopyInto(out *SolutionContainerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SolutionContainer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SolutionContainerList. +func (in *SolutionContainerList) DeepCopy() *SolutionContainerList { + if in == nil { + return nil + } + out := new(SolutionContainerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SolutionContainerList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SolutionContainerStatus) DeepCopyInto(out *SolutionContainerStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SolutionContainerStatus. +func (in *SolutionContainerStatus) DeepCopy() *SolutionContainerStatus { + if in == nil { + return nil + } + out := new(SolutionContainerStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SolutionList) DeepCopyInto(out *SolutionList) { *out = *in diff --git a/k8s/apis/workflow/v1/campaigncontainer_types.go b/k8s/apis/workflow/v1/campaigncontainer_types.go new file mode 100644 index 000000000..d21aaae82 --- /dev/null +++ b/k8s/apis/workflow/v1/campaigncontainer_types.go @@ -0,0 +1,41 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package v1 + +import ( + apimodel "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + k8smodel "github.com/eclipse-symphony/symphony/k8s/apis/model/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type CampaignContainerStatus struct { + Properties map[string]string `json:"properties"` + ProvisioningStatus apimodel.ProvisioningStatus `json:"provisioningStatus"` + LastModified metav1.Time `json:"lastModified,omitempty"` +} + +// +kubebuilder:object:root=true +// CampaignContainer is the Schema for the campaigns API +type CampaignContainer struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec k8smodel.CampaignContainerSpec `json:"spec,omitempty"` + Status CampaignContainerStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// CampaignContainerList contains a list of Campaign +type CampaignContainerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CampaignContainer `json:"items"` +} + +func init() { + SchemeBuilder.Register(&CampaignContainer{}, &CampaignContainerList{}) +} diff --git a/k8s/apis/workflow/v1/zz_generated.deepcopy.go b/k8s/apis/workflow/v1/zz_generated.deepcopy.go index 7cc2e8927..e35d3c07e 100644 --- a/k8s/apis/workflow/v1/zz_generated.deepcopy.go +++ b/k8s/apis/workflow/v1/zz_generated.deepcopy.go @@ -117,6 +117,89 @@ func (in *Campaign) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CampaignContainer) DeepCopyInto(out *CampaignContainer) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CampaignContainer. +func (in *CampaignContainer) DeepCopy() *CampaignContainer { + if in == nil { + return nil + } + out := new(CampaignContainer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CampaignContainer) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CampaignContainerList) DeepCopyInto(out *CampaignContainerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CampaignContainer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CampaignContainerList. +func (in *CampaignContainerList) DeepCopy() *CampaignContainerList { + if in == nil { + return nil + } + out := new(CampaignContainerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CampaignContainerList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CampaignContainerStatus) DeepCopyInto(out *CampaignContainerStatus) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.ProvisioningStatus.DeepCopyInto(&out.ProvisioningStatus) + in.LastModified.DeepCopyInto(&out.LastModified) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CampaignContainerStatus. +func (in *CampaignContainerStatus) DeepCopy() *CampaignContainerStatus { + if in == nil { + return nil + } + out := new(CampaignContainerStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CampaignList) DeepCopyInto(out *CampaignList) { *out = *in diff --git a/k8s/config/oss/crd/bases/fabric.symphony_targetcontainers.yaml b/k8s/config/oss/crd/bases/fabric.symphony_targetcontainers.yaml new file mode 100644 index 000000000..de18af67b --- /dev/null +++ b/k8s/config/oss/crd/bases/fabric.symphony_targetcontainers.yaml @@ -0,0 +1,119 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: targetcontainers.fabric.symphony +spec: + group: fabric.symphony + names: + kind: TargetContainer + listKind: TargetContainerList + plural: targetcontainers + singular: targetcontainer + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.properties.status + name: Status + type: string + name: v1 + schema: + openAPIV3Schema: + description: Target is the Schema for the targets API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + type: object + status: + description: TargetContainerStatus defines the observed state of Target + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + description: 'Important: Run "make" to regenerate code after modifying + this file' + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/k8s/config/oss/crd/bases/federation.symphony_catalogcontainers.yaml b/k8s/config/oss/crd/bases/federation.symphony_catalogcontainers.yaml new file mode 100644 index 000000000..b0ffbc140 --- /dev/null +++ b/k8s/config/oss/crd/bases/federation.symphony_catalogcontainers.yaml @@ -0,0 +1,113 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: catalogcontainers.federation.symphony +spec: + group: federation.symphony + names: + kind: CatalogContainer + listKind: CatalogContainerList + plural: catalogcontainers + singular: catalogcontainer + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: CatalogContainer is the Schema for the catalogs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/k8s/config/oss/crd/bases/federation.symphony_catalogs.yaml b/k8s/config/oss/crd/bases/federation.symphony_catalogs.yaml index defdd5d53..91ef81090 100644 --- a/k8s/config/oss/crd/bases/federation.symphony_catalogs.yaml +++ b/k8s/config/oss/crd/bases/federation.symphony_catalogs.yaml @@ -75,24 +75,12 @@ spec: properties: type: object x-kubernetes-preserve-unknown-fields: true - siteId: - type: string type: type: string required: - properties - - siteId - type type: object - status: - properties: - properties: - additionalProperties: - type: string - type: object - required: - - properties - type: object type: object served: true storage: true diff --git a/k8s/config/oss/crd/bases/solution.symphony_instancecontainers.yaml b/k8s/config/oss/crd/bases/solution.symphony_instancecontainers.yaml new file mode 100644 index 000000000..cf245b870 --- /dev/null +++ b/k8s/config/oss/crd/bases/solution.symphony_instancecontainers.yaml @@ -0,0 +1,113 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: instancecontainers.solution.symphony +spec: + group: solution.symphony + names: + kind: InstanceContainer + listKind: InstanceContainerList + plural: instancecontainers + singular: instancecontainer + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: InstanceContainer is the Schema for the InstanceContainers API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/k8s/config/oss/crd/bases/solution.symphony_solutioncontainers.yaml b/k8s/config/oss/crd/bases/solution.symphony_solutioncontainers.yaml new file mode 100644 index 000000000..7e2750806 --- /dev/null +++ b/k8s/config/oss/crd/bases/solution.symphony_solutioncontainers.yaml @@ -0,0 +1,113 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: solutioncontainers.solution.symphony +spec: + group: solution.symphony + names: + kind: SolutionContainer + listKind: SolutionContainerList + plural: solutioncontainers + singular: solutioncontainer + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: SolutionContainer is the Schema for the SolutionContainer API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/k8s/config/oss/crd/bases/solution.symphony_solutions.yaml b/k8s/config/oss/crd/bases/solution.symphony_solutions.yaml index 8231ad562..1186e4828 100644 --- a/k8s/config/oss/crd/bases/solution.symphony_solutions.yaml +++ b/k8s/config/oss/crd/bases/solution.symphony_solutions.yaml @@ -114,6 +114,8 @@ spec: additionalProperties: type: string type: object + rootResource: + type: string version: description: Defines the version of a particular resource type: string diff --git a/k8s/config/oss/crd/bases/workflow.symphony_campaigncontainers.yaml b/k8s/config/oss/crd/bases/workflow.symphony_campaigncontainers.yaml new file mode 100644 index 000000000..81fc7f457 --- /dev/null +++ b/k8s/config/oss/crd/bases/workflow.symphony_campaigncontainers.yaml @@ -0,0 +1,111 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: campaigncontainers.workflow.symphony +spec: + group: workflow.symphony + names: + kind: CampaignContainer + listKind: CampaignContainerList + plural: campaigncontainers + singular: campaigncontainer + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: CampaignContainer is the Schema for the campaigns API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true diff --git a/k8s/config/oss/crd/kustomization.yaml b/k8s/config/oss/crd/kustomization.yaml index ea55942c1..c94d5490c 100644 --- a/k8s/config/oss/crd/kustomization.yaml +++ b/k8s/config/oss/crd/kustomization.yaml @@ -20,6 +20,11 @@ resources: - bases/fabric.symphony_devices.yaml - bases/federation.symphony_sites.yaml - bases/federation.symphony_catalogs.yaml +- bases/solution.symphony_solutioncontainers.yaml +- bases/workflow.symphony_campaigncontainers.yaml +- bases/solution.symphony_instancecontainers.yaml +- bases/fabric.symphony_targetcontainers.yaml +- bases/federation.symphony_catalogcontainers.yaml #+kubebuilder:scaffold:crdkustomizeresource # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. diff --git a/k8s/config/oss/crd/patches/cainjection_in_fabric_targetcontainers.yaml b/k8s/config/oss/crd/patches/cainjection_in_fabric_targetcontainers.yaml new file mode 100644 index 000000000..0dda628e8 --- /dev/null +++ b/k8s/config/oss/crd/patches/cainjection_in_fabric_targetcontainers.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: targetcontainers.fabric.symphony diff --git a/k8s/config/oss/crd/patches/cainjection_in_federation_catalogcontainers.yaml b/k8s/config/oss/crd/patches/cainjection_in_federation_catalogcontainers.yaml new file mode 100644 index 000000000..0f955e70f --- /dev/null +++ b/k8s/config/oss/crd/patches/cainjection_in_federation_catalogcontainers.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: catalogcontainers.federation.symphony diff --git a/k8s/config/oss/crd/patches/cainjection_in_solution_instancecontainers.yaml b/k8s/config/oss/crd/patches/cainjection_in_solution_instancecontainers.yaml new file mode 100644 index 000000000..8cd79147b --- /dev/null +++ b/k8s/config/oss/crd/patches/cainjection_in_solution_instancecontainers.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: instancecontainers.solution.symphony diff --git a/k8s/config/oss/crd/patches/cainjection_in_solution_solutioncontainers.yaml b/k8s/config/oss/crd/patches/cainjection_in_solution_solutioncontainers.yaml new file mode 100644 index 000000000..7e544ebfb --- /dev/null +++ b/k8s/config/oss/crd/patches/cainjection_in_solution_solutioncontainers.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: solutioncontainers.solution.symphony diff --git a/k8s/config/oss/crd/patches/cainjection_in_workflow_campaigncontainers.yaml b/k8s/config/oss/crd/patches/cainjection_in_workflow_campaigncontainers.yaml new file mode 100644 index 000000000..0ffa26d91 --- /dev/null +++ b/k8s/config/oss/crd/patches/cainjection_in_workflow_campaigncontainers.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: campaigncontainers.workflow.symphony diff --git a/k8s/config/oss/crd/patches/webhook_in_fabric_targetcontainers.yaml b/k8s/config/oss/crd/patches/webhook_in_fabric_targetcontainers.yaml new file mode 100644 index 000000000..f14432749 --- /dev/null +++ b/k8s/config/oss/crd/patches/webhook_in_fabric_targetcontainers.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: targetcontainers.fabric.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/k8s/config/oss/crd/patches/webhook_in_federation_catalogcontainers.yaml b/k8s/config/oss/crd/patches/webhook_in_federation_catalogcontainers.yaml new file mode 100644 index 000000000..03b03c49f --- /dev/null +++ b/k8s/config/oss/crd/patches/webhook_in_federation_catalogcontainers.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: catalogcontainers.federation.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/k8s/config/oss/crd/patches/webhook_in_solution_instancecontainers.yaml b/k8s/config/oss/crd/patches/webhook_in_solution_instancecontainers.yaml new file mode 100644 index 000000000..1a776d61e --- /dev/null +++ b/k8s/config/oss/crd/patches/webhook_in_solution_instancecontainers.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: instancecontainers.solution.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/k8s/config/oss/crd/patches/webhook_in_solution_solutioncontainers.yaml b/k8s/config/oss/crd/patches/webhook_in_solution_solutioncontainers.yaml new file mode 100644 index 000000000..ea3447dc4 --- /dev/null +++ b/k8s/config/oss/crd/patches/webhook_in_solution_solutioncontainers.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: solutioncontainers.solution.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/k8s/config/oss/crd/patches/webhook_in_workflow_campaigncontainers.yaml b/k8s/config/oss/crd/patches/webhook_in_workflow_campaigncontainers.yaml new file mode 100644 index 000000000..f3b9a3b0f --- /dev/null +++ b/k8s/config/oss/crd/patches/webhook_in_workflow_campaigncontainers.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: campaigncontainers.workflow.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/k8s/config/oss/default/manager_webhook_patch.yaml b/k8s/config/oss/default/manager_webhook_patch.yaml index 3bacec8b6..074548e92 100644 --- a/k8s/config/oss/default/manager_webhook_patch.yaml +++ b/k8s/config/oss/default/manager_webhook_patch.yaml @@ -18,6 +18,11 @@ spec: name: webhook-server protocol: TCP volumeMounts: + - mountPath: /var/run/secrets/tokens + name: symphony-api-token + - mountPath: '{{ include "symphony.apiServingCertsDir" . }}' + name: api-ca-cert + readOnly: true - mountPath: /tmp/k8s-webhook-server/serving-certs name: cert readOnly: true diff --git a/k8s/config/oss/helm/manager-patch.yaml b/k8s/config/oss/helm/manager-patch.yaml index 521537dee..d44170fea 100644 --- a/k8s/config/oss/helm/manager-patch.yaml +++ b/k8s/config/oss/helm/manager-patch.yaml @@ -28,8 +28,31 @@ spec: value: "{{ .Chart.AppVersion }}" - name: CONFIG_NAME value: '{{ include "symphony.fullname" . }}-manager-config' + - name: SERVICE_ACCOUNT_NAME + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName + - name: USE_SERVICE_ACCOUNT_TOKENS + value: "true" + envFrom: + - configMapRef: + name: '{{ include "symphony.envConfigName" . }}' volumes: - name: cert secret: defaultMode: 420 - secretName: '{{ include "symphony.fullname" . }}-webhook-server-cert' \ No newline at end of file + secretName: '{{ include "symphony.fullname" . }}-webhook-server-cert' + - name: symphony-api-token + projected: + sources: + - serviceAccountToken: + audience: '{{ include "symphony.url" . }}' + expirationSeconds: 600 + path: symphony-api-token + - name: api-ca-cert + secret: + defaultMode: 420 + items: + - key: ca.crt + path: ca.crt + secretName: '{{ include "symphony.apiServingCertName" . }}' diff --git a/k8s/config/oss/rbac/fabric_targetcontainer_editor_role.yaml b/k8s/config/oss/rbac/fabric_targetcontainer_editor_role.yaml new file mode 100644 index 000000000..89421d54b --- /dev/null +++ b/k8s/config/oss/rbac/fabric_targetcontainer_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit targetcontainers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: targetcontainer-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: targetcontainer-editor-role +rules: +- apiGroups: + - fabric.symphony + resources: + - targetcontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - fabric.symphony + resources: + - targetcontainers/status + verbs: + - get diff --git a/k8s/config/oss/rbac/fabric_targetcontainer_viewer_role.yaml b/k8s/config/oss/rbac/fabric_targetcontainer_viewer_role.yaml new file mode 100644 index 000000000..e18885874 --- /dev/null +++ b/k8s/config/oss/rbac/fabric_targetcontainer_viewer_role.yaml @@ -0,0 +1,33 @@ +## +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. +## SPDX-License-Identifier: MIT +## + +# permissions for end users to view targetcontainers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: targetcontainer-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: targetcontainer-viewer-role +rules: +- apiGroups: + - fabric.symphony + resources: + - targetcontainers + verbs: + - get + - list + - watch +- apiGroups: + - fabric.symphony + resources: + - targetcontainers/status + verbs: + - get diff --git a/k8s/config/oss/rbac/federation_catalogcontainer_editor_role.yaml b/k8s/config/oss/rbac/federation_catalogcontainer_editor_role.yaml new file mode 100644 index 000000000..663ef0af2 --- /dev/null +++ b/k8s/config/oss/rbac/federation_catalogcontainer_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit catalogcontainers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: catalogcontainer-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: catalogcontainer-editor-role +rules: +- apiGroups: + - federation.symphony + resources: + - catalogcontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - federation.symphony + resources: + - catalogcontainers/status + verbs: + - get diff --git a/k8s/config/oss/rbac/federation_catalogcontainer_viewer_role.yaml b/k8s/config/oss/rbac/federation_catalogcontainer_viewer_role.yaml new file mode 100644 index 000000000..37ac285be --- /dev/null +++ b/k8s/config/oss/rbac/federation_catalogcontainer_viewer_role.yaml @@ -0,0 +1,33 @@ +## +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. +## SPDX-License-Identifier: MIT +## + +# permissions for end users to view catalogcontainers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: catalogcontainer-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: catalogcontainer-viewer-role +rules: +- apiGroups: + - federation.symphony + resources: + - catalogcontainers + verbs: + - get + - list + - watch +- apiGroups: + - federation.symphony + resources: + - catalogcontainers/status + verbs: + - get diff --git a/k8s/config/oss/rbac/role.yaml b/k8s/config/oss/rbac/role.yaml index e4801774a..4e68b4698 100644 --- a/k8s/config/oss/rbac/role.yaml +++ b/k8s/config/oss/rbac/role.yaml @@ -373,4 +373,134 @@ rules: verbs: - get - patch + - update +- apiGroups: + - workflow.symphony + resources: + - campaigncontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - workflow.symphony + resources: + - campaigncontainers/finalizers + verbs: + - update +- apiGroups: + - workflow.symphony + resources: + - campaigncontainers/status + verbs: + - get + - patch + - update +- apiGroups: + - solution.symphony + resources: + - instancecontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony + resources: + - instancecontainers/finalizers + verbs: + - update +- apiGroups: + - solution.symphony + resources: + - instancecontainers/status + verbs: + - get + - patch + - update +- apiGroups: + - solution.symphony + resources: + - solutioncontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony + resources: + - solutioncontainers/finalizers + verbs: + - update +- apiGroups: + - solution.symphony + resources: + - solutioncontainers/status + verbs: + - get + - patch + - update +- apiGroups: + - fabric.symphony + resources: + - targetcontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - fabric.symphony + resources: + - targetcontainers/finalizers + verbs: + - update +- apiGroups: + - fabric.symphony + resources: + - targetcontainers/status + verbs: + - get + - patch + - update +- apiGroups: + - federation.symphony + resources: + - catalogcontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - federation.symphony + resources: + - catalogcontainers/finalizers + verbs: + - update +- apiGroups: + - federation.symphony + resources: + - catalogcontainers/status + verbs: + - get + - patch - update \ No newline at end of file diff --git a/k8s/config/oss/rbac/solution_instancecontainer_editor_role.yaml b/k8s/config/oss/rbac/solution_instancecontainer_editor_role.yaml new file mode 100644 index 000000000..d61c6a749 --- /dev/null +++ b/k8s/config/oss/rbac/solution_instancecontainer_editor_role.yaml @@ -0,0 +1,37 @@ +## +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. +## SPDX-License-Identifier: MIT +## + +# permissions for end users to edit instancecontainers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: instancecontainer-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: instancecontainer-editor-role +rules: +- apiGroups: + - solution.symphony + resources: + - instancecontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony + resources: + - instancecontainers/status + verbs: + - get diff --git a/k8s/config/oss/rbac/solution_instancecontainer_viewer_role.yaml b/k8s/config/oss/rbac/solution_instancecontainer_viewer_role.yaml new file mode 100644 index 000000000..0d99bbee5 --- /dev/null +++ b/k8s/config/oss/rbac/solution_instancecontainer_viewer_role.yaml @@ -0,0 +1,33 @@ +## +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. +## SPDX-License-Identifier: MIT +## + +# permissions for end users to view instancecontainers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: instancecontainer-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: instancecontainer-viewer-role +rules: +- apiGroups: + - solution.symphony + resources: + - instancecontainers + verbs: + - get + - list + - watch +- apiGroups: + - solution.symphony + resources: + - instancecontainers/status + verbs: + - get diff --git a/k8s/config/oss/rbac/solution_solutioncontainer_editor_role.yaml b/k8s/config/oss/rbac/solution_solutioncontainer_editor_role.yaml new file mode 100644 index 000000000..f08185272 --- /dev/null +++ b/k8s/config/oss/rbac/solution_solutioncontainer_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit solutioncontainers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: solutioncontainer-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: solutioncontainer-editor-role +rules: +- apiGroups: + - solution.symphony + resources: + - solutioncontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony + resources: + - solutioncontainers/status + verbs: + - get diff --git a/k8s/config/oss/rbac/solution_solutioncontainer_viewer_role.yaml b/k8s/config/oss/rbac/solution_solutioncontainer_viewer_role.yaml new file mode 100644 index 000000000..732b90c8f --- /dev/null +++ b/k8s/config/oss/rbac/solution_solutioncontainer_viewer_role.yaml @@ -0,0 +1,33 @@ +## +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. +## SPDX-License-Identifier: MIT +## + +# permissions for end users to view solutioncontainers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: solutioncontainer-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: solutioncontainer-viewer-role +rules: +- apiGroups: + - solution.symphony + resources: + - solutioncontainers + verbs: + - get + - list + - watch +- apiGroups: + - solution.symphony + resources: + - solutioncontainers/status + verbs: + - get diff --git a/k8s/config/oss/rbac/workflow_campaigncontainer_editor_role.yaml b/k8s/config/oss/rbac/workflow_campaigncontainer_editor_role.yaml new file mode 100644 index 000000000..9b251ff00 --- /dev/null +++ b/k8s/config/oss/rbac/workflow_campaigncontainer_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit campaigncontainers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: campaigncontainer-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: campaigncontainer-editor-role +rules: +- apiGroups: + - workflow.symphony + resources: + - campaigncontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - workflow.symphony + resources: + - campaigncontainers/status + verbs: + - get diff --git a/k8s/config/oss/rbac/workflow_campaigncontainer_viewer_role.yaml b/k8s/config/oss/rbac/workflow_campaigncontainer_viewer_role.yaml new file mode 100644 index 000000000..707e21fd8 --- /dev/null +++ b/k8s/config/oss/rbac/workflow_campaigncontainer_viewer_role.yaml @@ -0,0 +1,33 @@ +## +## Copyright (c) Microsoft Corporation. +## Licensed under the MIT license. +## SPDX-License-Identifier: MIT +## + +# permissions for end users to view campaigncontainers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: campaigncontainer-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: symphony-k8s + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + name: campaigncontainer-viewer-role +rules: +- apiGroups: + - workflow.symphony + resources: + - campaigncontainers + verbs: + - get + - list + - watch +- apiGroups: + - workflow.symphony + resources: + - campaigncontainers/status + verbs: + - get diff --git a/k8s/config/oss/samples/fabric_v1_targetcontainer.yaml b/k8s/config/oss/samples/fabric_v1_targetcontainer.yaml new file mode 100644 index 000000000..acaf933b7 --- /dev/null +++ b/k8s/config/oss/samples/fabric_v1_targetcontainer.yaml @@ -0,0 +1,12 @@ +apiVersion: fabric.symphony/v1 +kind: TargetContainer +metadata: + labels: + app.kubernetes.io/name: targetcontainer + app.kubernetes.io/instance: targetcontainer-sample + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: symphony-k8s + name: targetcontainer-sample +spec: + # TODO(user): Add fields here diff --git a/k8s/config/oss/samples/federation_v1_catalogcontainer.yaml b/k8s/config/oss/samples/federation_v1_catalogcontainer.yaml new file mode 100644 index 000000000..8950ec6c9 --- /dev/null +++ b/k8s/config/oss/samples/federation_v1_catalogcontainer.yaml @@ -0,0 +1,12 @@ +apiVersion: federation.symphony/v1 +kind: CatalogContainer +metadata: + labels: + app.kubernetes.io/name: catalogcontainer + app.kubernetes.io/instance: catalogcontainer-sample + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: symphony-k8s + name: catalogcontainer-sample +spec: + # TODO(user): Add fields here diff --git a/k8s/config/oss/samples/solution_v1_instancecontainer.yaml b/k8s/config/oss/samples/solution_v1_instancecontainer.yaml new file mode 100644 index 000000000..3289cbb5d --- /dev/null +++ b/k8s/config/oss/samples/solution_v1_instancecontainer.yaml @@ -0,0 +1,12 @@ +apiVersion: solution.symphony/v1 +kind: InstanceContainer +metadata: + labels: + app.kubernetes.io/name: instancecontainer + app.kubernetes.io/instance: instancecontainer-sample + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: symphony-k8s + name: instancecontainer-sample +spec: + # TODO(user): Add fields here diff --git a/k8s/config/oss/samples/solution_v1_solutioncontainer.yaml b/k8s/config/oss/samples/solution_v1_solutioncontainer.yaml new file mode 100644 index 000000000..6b7ca4f89 --- /dev/null +++ b/k8s/config/oss/samples/solution_v1_solutioncontainer.yaml @@ -0,0 +1,12 @@ +apiVersion: solution.symphony/v1 +kind: SolutionContainer +metadata: + labels: + app.kubernetes.io/name: solutioncontainer + app.kubernetes.io/instance: solutioncontainer-sample + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: symphony-k8s + name: solutioncontainer-sample +spec: + # TODO(user): Add fields here diff --git a/k8s/config/oss/samples/workflow_v1_campaigncontainer.yaml b/k8s/config/oss/samples/workflow_v1_campaigncontainer.yaml new file mode 100644 index 000000000..bcbacd494 --- /dev/null +++ b/k8s/config/oss/samples/workflow_v1_campaigncontainer.yaml @@ -0,0 +1,12 @@ +apiVersion: workflow.symphony/v1 +kind: CampaignContainer +metadata: + labels: + app.kubernetes.io/name: campaigncontainer + app.kubernetes.io/instance: campaigncontainer-sample + app.kubernetes.io/part-of: symphony-k8s + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: symphony-k8s + name: campaigncontainer-sample +spec: + # TODO(user): Add fields here diff --git a/k8s/controllers/fabric/targetcontainer_controller.go b/k8s/controllers/fabric/targetcontainer_controller.go new file mode 100644 index 000000000..2a8bd527a --- /dev/null +++ b/k8s/controllers/fabric/targetcontainer_controller.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package fabric + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + fabricv1 "gopls-workspace/apis/fabric/v1" +) + +// TargetContainerReconciler reconciles a TargetContainer object +type TargetContainerReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=fabric.symphony,resources=targetcontainers,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=fabric.symphony,resources=targetcontainers/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=fabric.symphony,resources=targetcontainers/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the TargetContainer object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile +func (r *TargetContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *TargetContainerReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&fabricv1.TargetContainer{}). + Complete(r) +} diff --git a/k8s/controllers/federation/catalogcontainer_controller.go b/k8s/controllers/federation/catalogcontainer_controller.go new file mode 100644 index 000000000..e048d37e6 --- /dev/null +++ b/k8s/controllers/federation/catalogcontainer_controller.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package federation + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + federationv1 "gopls-workspace/apis/federation/v1" +) + +// CatalogContainerReconciler reconciles a CatalogContainer object +type CatalogContainerReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=federation.symphony,resources=catalogcontainers,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=federation.symphony,resources=catalogcontainers/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=federation.symphony,resources=catalogcontainers/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the CatalogContainer object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile +func (r *CatalogContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *CatalogContainerReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&federationv1.CatalogContainer{}). + Complete(r) +} diff --git a/k8s/controllers/solution/instancecontainer_controller.go b/k8s/controllers/solution/instancecontainer_controller.go new file mode 100644 index 000000000..08a5a3c4d --- /dev/null +++ b/k8s/controllers/solution/instancecontainer_controller.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package solution + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + solutionv1 "gopls-workspace/apis/solution/v1" +) + +// InstanceContainerReconciler reconciles a InstanceContainer object +type InstanceContainerReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=solution.symphony,resources=instancecontainers,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=solution.symphony,resources=instancecontainers/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=solution.symphony,resources=instancecontainers/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the InstanceContainer object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile +func (r *InstanceContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *InstanceContainerReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&solutionv1.InstanceContainer{}). + Complete(r) +} diff --git a/k8s/controllers/solution/solution_controller.go b/k8s/controllers/solution/solution_controller.go index 7d26d33a9..31e1a42ce 100644 --- a/k8s/controllers/solution/solution_controller.go +++ b/k8s/controllers/solution/solution_controller.go @@ -8,13 +8,16 @@ package solution import ( "context" + "encoding/json" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" solutionv1 "gopls-workspace/apis/solution/v1" + + api_utils "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/utils" ) // SolutionReconciler reconciles a Solution object @@ -37,9 +40,35 @@ type SolutionReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.0/pkg/reconcile func (r *SolutionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + log := ctrllog.FromContext(ctx) + log.Info("Reconcile Solution") + + // Get instance + solution := &solutionv1.Solution{} + if err := r.Client.Get(ctx, req.NamespacedName, solution); err != nil { + log.Error(err, "unable to fetch Solution object") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if solution.Status.Properties == nil { + solution.Status.Properties = make(map[string]string) + } - // TODO(user): your logic here + version := solution.Spec.Version + name := solution.Spec.RootResource + solutionName := name + ":" + version + jData, _ := json.Marshal(solution) + if solution.ObjectMeta.DeletionTimestamp.IsZero() { // update + err := api_utils.UpsertSolution(ctx, "http://symphony-service:8080/v1alpha2/", solutionName, "admin", "", jData, req.Namespace) + if err != nil { + return ctrl.Result{}, err + } + } else { // delete + err := api_utils.DeleteSolution(ctx, "http://symphony-service:8080/v1alpha2/", solutionName, "admin", "", req.Namespace) + if err != nil { + return ctrl.Result{}, err + } + } return ctrl.Result{}, nil } diff --git a/k8s/controllers/solution/solutioncontainer_controller.go b/k8s/controllers/solution/solutioncontainer_controller.go new file mode 100644 index 000000000..97f0691f4 --- /dev/null +++ b/k8s/controllers/solution/solutioncontainer_controller.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package solution + +import ( + "context" + + solutionv1 "gopls-workspace/apis/solution/v1" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// SolutionContainerReconciler reconciles a SolutionContainer object +type SolutionContainerReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=solution.symphony,resources=solutioncontainers,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=solution.symphony,resources=solutioncontainers/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=solution.symphony,resources=solutioncontainers/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the SolutionContainer object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile +func (r *SolutionContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *SolutionContainerReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&solutionv1.SolutionContainer{}). + Complete(r) +} diff --git a/k8s/controllers/workflow/campaigncontainer_controller.go b/k8s/controllers/workflow/campaigncontainer_controller.go new file mode 100644 index 000000000..7bfd80aab --- /dev/null +++ b/k8s/controllers/workflow/campaigncontainer_controller.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package workflow + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + workflowv1 "gopls-workspace/apis/workflow/v1" +) + +// CampaignContainerReconciler reconciles a CampaignContainer object +type CampaignContainerReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=workflow.symphony,resources=campaigncontainers,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=workflow.symphony,resources=campaigncontainers/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=workflow.symphony,resources=campaigncontainers/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the CampaignContainer object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile +func (r *CampaignContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *CampaignContainerReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&workflowv1.CampaignContainer{}). + Complete(r) +} diff --git a/k8s/magefile.go b/k8s/magefile.go index fd0f1814e..ebdcfd181 100644 --- a/k8s/magefile.go +++ b/k8s/magefile.go @@ -46,7 +46,7 @@ func Manifests() error { mg.Deps(ensureControllerGen) return shellcmd.RunAll( shellcmd.Command("rm -rf config/oss/crd/bases"), - controllerGen.Command("rbac:roleName=manager-role crd webhook paths=./apis/ai/v1 paths=./apis/fabric/v1 paths=./apis/solution/v1 paths=./apis/workflow/v1 paths=./apis/federation/v1 output:crd:artifacts:config=config/oss/crd/bases output:webhook:artifacts:config=config/oss/webhook"), + controllerGen.Command("rbac:roleName=manager-role crd webhook paths=./apis/ai/v1 paths=./apis/fabric/v1 paths=./apis/solution/v1 paths=./apis/workflow/v1 paths=./apis/federation/v1 output:crd:artifacts:config=config/oss/crd/bases output:webhook:artifacts:config=config/oss/webhook output:rbac:artifacts:config=config/oss/rbac"), ) } @@ -99,7 +99,7 @@ func Run() error { // Kustomize startup symphony yaml for helm chart. func HelmTemplate() error { mg.Deps(ensureKustomize, Manifests) - return kustomize.Command("build config/oss/helm -o ../packages/helm/symphony/templates/symphony.yaml").Run() + return kustomize.Command("build config/oss/helm -o ../packages/helm/symphony/templates/symphony-core/symphonyk8s.yaml").Run() } // Install CRDs into the K8s cluster specified in ~/.kube/config. diff --git a/k8s/main.go b/k8s/main.go index 07a805471..26cf73afd 100644 --- a/k8s/main.go +++ b/k8s/main.go @@ -297,6 +297,41 @@ func main() { os.Exit(1) } } + if err = (&solutioncontrollers.SolutionContainerReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "SolutionContainer") + os.Exit(1) + } + if err = (&federationcontrollers.CatalogContainerReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "CatalogContainer") + os.Exit(1) + } + if err = (&fabriccontrollers.TargetContainerReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "TargetContainer") + os.Exit(1) + } + if err = (&workflowcontrollers.CampaignContainerReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "CampaignContainer") + os.Exit(1) + } + if err = (&solutioncontrollers.InstanceContainerReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "InstanceContainer") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/packages/go.work b/packages/go.work new file mode 100644 index 000000000..74a22c9ef --- /dev/null +++ b/packages/go.work @@ -0,0 +1,6 @@ +go 1.19 + +use ( + ./mage + ./testutils +) diff --git a/packages/go.work.sum b/packages/go.work.sum new file mode 100644 index 000000000..717ae5aea --- /dev/null +++ b/packages/go.work.sum @@ -0,0 +1,11 @@ +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/cheggaaa/pb v2.0.7+incompatible h1:gLKifR1UkZ/kLkda5gC0K6c8g+jU2sINPtBeOiNlMhU= +github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= +google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M= diff --git a/packages/helm/symphony/azure/metadata.json b/packages/helm/symphony/azure/metadata.json deleted file mode 100644 index 4055ae7f0..000000000 --- a/packages/helm/symphony/azure/metadata.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "mappings": [ - { - "ApiVersion": "2020-01-01-preview", - "ResourceType": "solutions", - "ProviderName": "Private.Symphony", - "ResourceMapping": { - "Version": "v1", - "Group": "symphony.microsoft.com", - "Kind": "Solution", - "Name": "solutions.symphony.microsoft.com" - } - }, - { - "ApiVersion": "2020-01-01-preview", - "ResourceType": "instances", - "ResourceProviderName": "Private.Symphony", - "ResourceMapping": { - "Version": "v1", - "Group": "symphony.microsoft.com", - "Kind": "Instance", - "Name": "instances.symphony.microsoft.com", - "IsAsync": true - } - }, - { - "ApiVersion": "2020-01-01-preview", - "ResourceType": "targets", - "ResourceProviderName": "Private.Symphony", - "ResourceMapping": { - "Version": "v1", - "Group": "symphony.microsoft.com", - "Kind": "Target", - "Name": "targets.symphony.microsoft.com", - "IsAsync": true - } - } - ] -} diff --git a/packages/helm/symphony/crds/cert-manager.crds.yaml b/packages/helm/symphony/crds/cert-manager.crds.yaml index 15af8a5ba..d45337eba 100644 --- a/packages/helm/symphony/crds/cert-manager.crds.yaml +++ b/packages/helm/symphony/crds/cert-manager.crds.yaml @@ -16,31 +16,46 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: clusterissuers.cert-manager.io + name: certificaterequests.cert-manager.io labels: app: 'cert-manager' app.kubernetes.io/name: 'cert-manager' app.kubernetes.io/instance: 'cert-manager' # Generated labels - app.kubernetes.io/version: "v1.11.0" + app.kubernetes.io/version: "v1.13.1" spec: group: cert-manager.io names: - kind: ClusterIssuer - listKind: ClusterIssuerList - plural: clusterissuers - singular: clusterissuer + kind: CertificateRequest + listKind: CertificateRequestList + plural: certificaterequests + shortNames: + - cr + - crs + singular: certificaterequest categories: - cert-manager - scope: Cluster + scope: Namespaced versions: - name: v1 subresources: status: {} additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Approved")].status + name: Approved + type: string + - jsonPath: .status.conditions[?(@.type=="Denied")].status + name: Denied + type: string - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + type: string + - jsonPath: .spec.username + name: Requestor + type: string - jsonPath: .status.conditions[?(@.type=="Ready")].message name: Status priority: 1 @@ -51,10 +66,8 @@ spec: type: date schema: openAPIV3Schema: - description: A ClusterIssuer represents a certificate issuing authority which can be referenced as part of `issuerRef` fields. It is similar to an Issuer, however it is cluster-scoped and therefore can be referenced by resources that exist in *any* namespace, not just the same namespace as the referent. + description: "A CertificateRequest is used to request a signed certificate from one of the configured issuers. \n All fields within the CertificateRequest's `spec` are immutable after creation. A CertificateRequest will either succeed or fail, as denoted by its `Ready` status condition and its `status.failureTime` field. \n A CertificateRequest is a one-shot resource, meaning it represents a single point in time request for a certificate and cannot be re-used." type: object - required: - - spec properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' @@ -65,1293 +78,1629 @@ spec: metadata: type: object spec: - description: Desired state of the ClusterIssuer resource. + description: Specification of the desired state of the CertificateRequest resource. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status type: object + required: + - issuerRef + - request properties: - acme: - description: ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates. + duration: + description: Requested 'duration' (i.e. lifetime) of the Certificate. Note that the issuer may choose to ignore the requested duration, just like any other requested attribute. + type: string + extra: + description: Extra contains extra attributes of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: object + additionalProperties: + type: array + items: + type: string + groups: + description: Groups contains group membership of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: array + items: + type: string + x-kubernetes-list-type: atomic + isCA: + description: "Requested basic constraints isCA value. Note that the issuer may choose to ignore the requested isCA value, just like any other requested attribute. \n NOTE: If the CSR in the `Request` field has a BasicConstraints extension, it must have the same isCA value as specified here. \n If true, this will automatically add the `cert sign` usage to the list of requested `usages`." + type: boolean + issuerRef: + description: "Reference to the issuer responsible for issuing the certificate. If the issuer is namespace-scoped, it must be in the same namespace as the Certificate. If the issuer is cluster-scoped, it can be used from any namespace. \n The `name` field of the reference must always be specified." type: object required: - - privateKeySecretRef - - server + - name properties: - caBundle: - description: Base64-encoded bundle of PEM CAs which can be used to validate the certificate chain presented by the ACME server. Mutually exclusive with SkipTLSVerify; prefer using CABundle to prevent various kinds of security vulnerabilities. If CABundle and SkipTLSVerify are unset, the system certificate bundle inside the container is used to validate the TLS connection. - type: string - format: byte - disableAccountKeyGeneration: - description: Enables or disables generating a new ACME account key. If true, the Issuer resource will *not* request a new account but will expect the account key to be supplied via an existing secret. If false, the cert-manager system will generate a new ACME account key for the Issuer. Defaults to false. - type: boolean - email: - description: Email is the email address to be associated with the ACME account. This field is optional, but it is strongly recommended to be set. It will be used to contact you in case of issues with your account or certificates, including expiry notification emails. This field may be updated after the account is initially registered. + group: + description: Group of the resource being referred to. type: string - enableDurationFeature: - description: Enables requesting a Not After date on certificates that matches the duration of the certificate. This is not supported by all ACME servers like Let's Encrypt. If set to true when the ACME server does not support it it will create an error on the Order. Defaults to false. - type: boolean - externalAccountBinding: - description: ExternalAccountBinding is a reference to a CA external account of the ACME server. If set, upon registration cert-manager will attempt to associate the given external account credentials with the registered ACME account. - type: object - required: - - keyID - - keySecretRef - properties: - keyAlgorithm: - description: 'Deprecated: keyAlgorithm field exists for historical compatibility reasons and should not be used. The algorithm is now hardcoded to HS256 in golang/x/crypto/acme.' - type: string - enum: - - HS256 - - HS384 - - HS512 - keyID: - description: keyID is the ID of the CA key that the External Account is bound to. - type: string - keySecretRef: - description: keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes Secret which holds the symmetric MAC key of the External Account Binding. The `key` is the index string that is paired with the key data in the Secret and should not be confused with the key data itself, or indeed with the External Account Binding keyID above. The secret key stored in the Secret **must** be un-padded, base64 URL encoded data. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - preferredChain: - description: 'PreferredChain is the chain to use if the ACME server outputs multiple. PreferredChain is no guarantee that this one gets delivered by the ACME endpoint. For example, for Let''s Encrypt''s DST crosssign you would use: "DST Root CA X3" or "ISRG Root X1" for the newer Let''s Encrypt root CA. This value picks the first certificate bundle in the ACME alternative chains that has a certificate with this value as its issuer''s CN' + kind: + description: Kind of the resource being referred to. type: string - maxLength: 64 - privateKeySecretRef: - description: PrivateKey is the name of a Kubernetes Secret resource that will be used to store the automatically generated ACME account private key. Optionally, a `key` may be specified to select a specific entry within the named Secret resource. If `key` is not specified, a default of `tls.key` will be used. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - server: - description: 'Server is the URL used to access the ACME server''s ''directory'' endpoint. For example, for Let''s Encrypt''s staging endpoint, you would use: "https://acme-staging-v02.api.letsencrypt.org/directory". Only ACME v2 endpoints (i.e. RFC 8555) are supported.' + name: + description: Name of the resource being referred to. type: string - skipTLSVerify: - description: 'INSECURE: Enables or disables validation of the ACME server TLS certificate. If true, requests to the ACME server will not have the TLS certificate chain validated. Mutually exclusive with CABundle; prefer using CABundle to prevent various kinds of security vulnerabilities. Only enable this option in development environments. If CABundle and SkipTLSVerify are unset, the system certificate bundle inside the container is used to validate the TLS connection. Defaults to false.' - type: boolean - solvers: - description: 'Solvers is a list of challenge solvers that will be used to solve ACME challenges for the matching domains. Solver configurations must be provided in order to obtain certificates from an ACME server. For more information, see: https://cert-manager.io/docs/configuration/acme/' - type: array - items: - description: An ACMEChallengeSolver describes how to solve ACME challenges for the issuer it is part of. A selector may be provided to use different solving strategies for different DNS names. Only one of HTTP01 or DNS01 must be provided. - type: object - properties: - dns01: - description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. - type: object - properties: - acmeDNS: - description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. - type: object - required: - - accountSecretRef - - host - properties: - accountSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - host: - type: string - akamai: - description: Use the Akamai DNS zone management API to manage DNS01 challenge records. - type: object - required: - - accessTokenSecretRef - - clientSecretSecretRef - - clientTokenSecretRef - - serviceConsumerDomain - properties: - accessTokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - clientSecretSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - clientTokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - serviceConsumerDomain: - type: string - azureDNS: - description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. - type: object - required: - - resourceGroupName - - subscriptionID - properties: - clientID: - description: if both this and ClientSecret are left unset MSI will be used - type: string - clientSecretSecretRef: - description: if both this and ClientID are left unset MSI will be used - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - environment: - description: name of the Azure environment (default AzurePublicCloud) - type: string - enum: - - AzurePublicCloud - - AzureChinaCloud - - AzureGermanCloud - - AzureUSGovernmentCloud - hostedZoneName: - description: name of the DNS zone that should be used - type: string - managedIdentity: - description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID - type: object - properties: - clientID: - description: client ID of the managed identity, can not be used at the same time as resourceID - type: string - resourceID: - description: resource ID of the managed identity, can not be used at the same time as clientID - type: string - resourceGroupName: - description: resource group the DNS zone is located in - type: string - subscriptionID: - description: ID of the Azure subscription - type: string - tenantID: - description: when specifying ClientID and ClientSecret then this field is also needed - type: string - cloudDNS: - description: Use the Google Cloud DNS API to manage DNS01 challenge records. - type: object - required: - - project - properties: - hostedZoneName: - description: HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone. - type: string - project: - type: string - serviceAccountSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - cloudflare: - description: Use the Cloudflare API to manage DNS01 challenge records. - type: object - properties: - apiKeySecretRef: - description: 'API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.' - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - apiTokenSecretRef: - description: API token used to authenticate with Cloudflare. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - email: - description: Email of the account, only required when using API key based authentication. - type: string - cnameStrategy: - description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. + request: + description: "The PEM-encoded X.509 certificate signing request to be submitted to the issuer for signing. \n If the CSR has a BasicConstraints extension, its isCA attribute must match the `isCA` value of this CertificateRequest. If the CSR has a KeyUsage extension, its key usages must match the key usages in the `usages` field of this CertificateRequest. If the CSR has a ExtKeyUsage extension, its extended key usages must match the extended key usages in the `usages` field of this CertificateRequest." + type: string + format: byte + uid: + description: UID contains the uid of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: string + usages: + description: "Requested key usages and extended key usages. \n NOTE: If the CSR in the `Request` field has uses the KeyUsage or ExtKeyUsage extension, these extensions must have the same values as specified here without any additional values. \n If unset, defaults to `digital signature` and `key encipherment`." + type: array + items: + description: "KeyUsage specifies valid usage contexts for keys. See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 https://tools.ietf.org/html/rfc5280#section-4.2.1.12 \n Valid KeyUsage values are as follows: \"signing\", \"digital signature\", \"content commitment\", \"key encipherment\", \"key agreement\", \"data encipherment\", \"cert sign\", \"crl sign\", \"encipher only\", \"decipher only\", \"any\", \"server auth\", \"client auth\", \"code signing\", \"email protection\", \"s/mime\", \"ipsec end system\", \"ipsec tunnel\", \"ipsec user\", \"timestamping\", \"ocsp signing\", \"microsoft sgc\", \"netscape sgc\"" + type: string + enum: + - signing + - digital signature + - content commitment + - key encipherment + - key agreement + - data encipherment + - cert sign + - crl sign + - encipher only + - decipher only + - any + - server auth + - client auth + - code signing + - email protection + - s/mime + - ipsec end system + - ipsec tunnel + - ipsec user + - timestamping + - ocsp signing + - microsoft sgc + - netscape sgc + username: + description: Username contains the name of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. + type: string + status: + description: 'Status of the CertificateRequest. This is set and managed automatically. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status' + type: object + properties: + ca: + description: The PEM encoded X.509 certificate of the signer, also known as the CA (Certificate Authority). This is set on a best-effort basis by different issuers. If not set, the CA is assumed to be unknown/not available. + type: string + format: byte + certificate: + description: The PEM encoded X.509 certificate resulting from the certificate signing request. If not set, the CertificateRequest has either not been completed or has failed. More information on failure can be found by checking the `conditions` field. + type: string + format: byte + conditions: + description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`, `InvalidRequest`, `Approved` and `Denied`. + type: array + items: + description: CertificateRequestCondition contains condition information for a CertificateRequest. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. + type: string + format: date-time + message: + description: Message is a human readable description of the details of the last transition, complementing reason. + type: string + reason: + description: Reason is a brief machine readable explanation for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: Type of the condition, known values are (`Ready`, `InvalidRequest`, `Approved`, `Denied`). + type: string + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failureTime: + description: FailureTime stores the time that this CertificateRequest failed. This is used to influence garbage collection and back-off. + type: string + format: date-time + served: true + storage: true +--- +# Source: cert-manager/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: certificates.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.13.1" +spec: + group: cert-manager.io + names: + kind: Certificate + listKind: CertificateList + plural: certificates + shortNames: + - cert + - certs + singular: certificate + categories: + - cert-manager + scope: Namespaced + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .spec.secretName + name: Secret + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + name: Age + type: date + schema: + openAPIV3Schema: + description: "A Certificate resource should be created to ensure an up to date and signed X.509 certificate is stored in the Kubernetes Secret resource named in `spec.secretName`. \n The stored certificate will be renewed before it expires (as configured by `spec.renewBefore`)." + type: object + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Specification of the desired state of the Certificate resource. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + type: object + required: + - issuerRef + - secretName + properties: + additionalOutputFormats: + description: "Defines extra output formats of the private key and signed certificate chain to be written to this Certificate's target Secret. \n This is an Alpha Feature and is only enabled with the `--feature-gates=AdditionalCertificateOutputFormats=true` option set on both the controller and webhook components." + type: array + items: + description: CertificateAdditionalOutputFormat defines an additional output format of a Certificate resource. These contain supplementary data formats of the signed certificate chain and paired private key. + type: object + required: + - type + properties: + type: + description: Type is the name of the format type that should be written to the Certificate's target Secret. + type: string + enum: + - DER + - CombinedPEM + commonName: + description: "Requested common name X509 certificate subject attribute. More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 NOTE: TLS clients will ignore this value when any subject alternative name is set (see https://tools.ietf.org/html/rfc6125#section-6.4.4). \n Should have a length of 64 characters or fewer to avoid generating invalid CSRs. Cannot be set if the `literalSubject` field is set." + type: string + dnsNames: + description: Requested DNS subject alternative names. + type: array + items: + type: string + duration: + description: "Requested 'duration' (i.e. lifetime) of the Certificate. Note that the issuer may choose to ignore the requested duration, just like any other requested attribute. \n If unset, this defaults to 90 days. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration." + type: string + emailAddresses: + description: Requested email subject alternative names. + type: array + items: + type: string + encodeUsagesInRequest: + description: "Whether the KeyUsage and ExtKeyUsage extensions should be set in the encoded CSR. \n This option defaults to true, and should only be disabled if the target issuer does not support CSRs with these X509 KeyUsage/ ExtKeyUsage extensions." + type: boolean + ipAddresses: + description: Requested IP address subject alternative names. + type: array + items: + type: string + isCA: + description: "Requested basic constraints isCA value. The isCA value is used to set the `isCA` field on the created CertificateRequest resources. Note that the issuer may choose to ignore the requested isCA value, just like any other requested attribute. \n If true, this will automatically add the `cert sign` usage to the list of requested `usages`." + type: boolean + issuerRef: + description: "Reference to the issuer responsible for issuing the certificate. If the issuer is namespace-scoped, it must be in the same namespace as the Certificate. If the issuer is cluster-scoped, it can be used from any namespace. \n The `name` field of the reference must always be specified." + type: object + required: + - name + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + keystores: + description: Additional keystore output formats to be stored in the Certificate's Secret. + type: object + properties: + jks: + description: JKS configures options for storing a JKS keystore in the `spec.secretName` Secret resource. + type: object + required: + - create + - passwordSecretRef + properties: + create: + description: Create enables JKS keystore creation for the Certificate. If true, a file named `keystore.jks` will be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef`. The keystore file will be updated immediately. If the issuer provided a CA certificate, a file named `truststore.jks` will also be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef` containing the issuing Certificate Authority + type: boolean + passwordSecretRef: + description: PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the JKS keystore. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + pkcs12: + description: PKCS12 configures options for storing a PKCS12 keystore in the `spec.secretName` Secret resource. + type: object + required: + - create + - passwordSecretRef + properties: + create: + description: Create enables PKCS12 keystore creation for the Certificate. If true, a file named `keystore.p12` will be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef`. The keystore file will be updated immediately. If the issuer provided a CA certificate, a file named `truststore.p12` will also be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef` containing the issuing Certificate Authority + type: boolean + passwordSecretRef: + description: PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the PKCS12 keystore. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + literalSubject: + description: "Requested X.509 certificate subject, represented using the LDAP \"String Representation of a Distinguished Name\" [1]. Important: the LDAP string format also specifies the order of the attributes in the subject, this is important when issuing certs for LDAP authentication. Example: `CN=foo,DC=corp,DC=example,DC=com` More info [1]: https://datatracker.ietf.org/doc/html/rfc4514 More info: https://github.com/cert-manager/cert-manager/issues/3203 More info: https://github.com/cert-manager/cert-manager/issues/4424 \n Cannot be set if the `subject` or `commonName` field is set. This is an Alpha Feature and is only enabled with the `--feature-gates=LiteralCertificateSubject=true` option set on both the controller and webhook components." + type: string + privateKey: + description: Private key options. These include the key algorithm and size, the used encoding and the rotation policy. + type: object + properties: + algorithm: + description: "Algorithm is the private key algorithm of the corresponding private key for this certificate. \n If provided, allowed values are either `RSA`, `ECDSA` or `Ed25519`. If `algorithm` is specified and `size` is not provided, key size of 2048 will be used for `RSA` key algorithm and key size of 256 will be used for `ECDSA` key algorithm. key size is ignored when using the `Ed25519` key algorithm." + type: string + enum: + - RSA + - ECDSA + - Ed25519 + encoding: + description: "The private key cryptography standards (PKCS) encoding for this certificate's private key to be encoded in. \n If provided, allowed values are `PKCS1` and `PKCS8` standing for PKCS#1 and PKCS#8, respectively. Defaults to `PKCS1` if not specified." + type: string + enum: + - PKCS1 + - PKCS8 + rotationPolicy: + description: "RotationPolicy controls how private keys should be regenerated when a re-issuance is being processed. \n If set to `Never`, a private key will only be generated if one does not already exist in the target `spec.secretName`. If one does exists but it does not have the correct algorithm or size, a warning will be raised to await user intervention. If set to `Always`, a private key matching the specified requirements will be generated whenever a re-issuance occurs. Default is `Never` for backward compatibility." + type: string + enum: + - Never + - Always + size: + description: "Size is the key bit size of the corresponding private key for this certificate. \n If `algorithm` is set to `RSA`, valid values are `2048`, `4096` or `8192`, and will default to `2048` if not specified. If `algorithm` is set to `ECDSA`, valid values are `256`, `384` or `521`, and will default to `256` if not specified. If `algorithm` is set to `Ed25519`, Size is ignored. No other values are allowed." + type: integer + renewBefore: + description: "How long before the currently issued certificate's expiry cert-manager should renew the certificate. For example, if a certificate is valid for 60 minutes, and `renewBefore=10m`, cert-manager will begin to attempt to renew the certificate 50 minutes after it was issued (i.e. when there are 10 minutes remaining until the certificate is no longer valid). \n NOTE: The actual lifetime of the issued certificate is used to determine the renewal time. If an issuer returns a certificate with a different lifetime than the one requested, cert-manager will use the lifetime of the issued certificate. \n If unset, this defaults to 1/3 of the issued certificate's lifetime. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration." + type: string + revisionHistoryLimit: + description: "The maximum number of CertificateRequest revisions that are maintained in the Certificate's history. Each revision represents a single `CertificateRequest` created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. \n If set, revisionHistoryLimit must be a value of `1` or greater. If unset (`nil`), revisions will not be garbage collected. Default value is `nil`." + type: integer + format: int32 + secretName: + description: Name of the Secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer. The Secret resource lives in the same namespace as the Certificate resource. + type: string + secretTemplate: + description: Defines annotations and labels to be copied to the Certificate's Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate's Secret. + type: object + properties: + annotations: + description: Annotations is a key value map to be copied to the target Kubernetes Secret. + type: object + additionalProperties: + type: string + labels: + description: Labels is a key value map to be copied to the target Kubernetes Secret. + type: object + additionalProperties: + type: string + subject: + description: "Requested set of X509 certificate subject attributes. More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6 \n The common name attribute is specified separately in the `commonName` field. Cannot be set if the `literalSubject` field is set." + type: object + properties: + countries: + description: Countries to be used on the Certificate. + type: array + items: + type: string + localities: + description: Cities to be used on the Certificate. + type: array + items: + type: string + organizationalUnits: + description: Organizational Units to be used on the Certificate. + type: array + items: + type: string + organizations: + description: Organizations to be used on the Certificate. + type: array + items: + type: string + postalCodes: + description: Postal codes to be used on the Certificate. + type: array + items: + type: string + provinces: + description: State/Provinces to be used on the Certificate. + type: array + items: + type: string + serialNumber: + description: Serial number to be used on the Certificate. + type: string + streetAddresses: + description: Street addresses to be used on the Certificate. + type: array + items: + type: string + uris: + description: Requested URI subject alternative names. + type: array + items: + type: string + usages: + description: "Requested key usages and extended key usages. These usages are used to set the `usages` field on the created CertificateRequest resources. If `encodeUsagesInRequest` is unset or set to `true`, the usages will additionally be encoded in the `request` field which contains the CSR blob. \n If unset, defaults to `digital signature` and `key encipherment`." + type: array + items: + description: "KeyUsage specifies valid usage contexts for keys. See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 https://tools.ietf.org/html/rfc5280#section-4.2.1.12 \n Valid KeyUsage values are as follows: \"signing\", \"digital signature\", \"content commitment\", \"key encipherment\", \"key agreement\", \"data encipherment\", \"cert sign\", \"crl sign\", \"encipher only\", \"decipher only\", \"any\", \"server auth\", \"client auth\", \"code signing\", \"email protection\", \"s/mime\", \"ipsec end system\", \"ipsec tunnel\", \"ipsec user\", \"timestamping\", \"ocsp signing\", \"microsoft sgc\", \"netscape sgc\"" + type: string + enum: + - signing + - digital signature + - content commitment + - key encipherment + - key agreement + - data encipherment + - cert sign + - crl sign + - encipher only + - decipher only + - any + - server auth + - client auth + - code signing + - email protection + - s/mime + - ipsec end system + - ipsec tunnel + - ipsec user + - timestamping + - ocsp signing + - microsoft sgc + - netscape sgc + status: + description: 'Status of the Certificate. This is set and managed automatically. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status' + type: object + properties: + conditions: + description: List of status conditions to indicate the status of certificates. Known condition types are `Ready` and `Issuing`. + type: array + items: + description: CertificateCondition contains condition information for an Certificate. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. + type: string + format: date-time + message: + description: Message is a human readable description of the details of the last transition, complementing reason. + type: string + observedGeneration: + description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Certificate. + type: integer + format: int64 + reason: + description: Reason is a brief machine readable explanation for the condition's last transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: Type of the condition, known values are (`Ready`, `Issuing`). + type: string + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failedIssuanceAttempts: + description: The number of continuous failed issuance attempts up till now. This field gets removed (if set) on a successful issuance and gets set to 1 if unset and an issuance has failed. If an issuance has failed, the delay till the next issuance will be calculated using formula time.Hour * 2 ^ (failedIssuanceAttempts - 1). + type: integer + lastFailureTime: + description: LastFailureTime is set only if the lastest issuance for this Certificate failed and contains the time of the failure. If an issuance has failed, the delay till the next issuance will be calculated using formula time.Hour * 2 ^ (failedIssuanceAttempts - 1). If the latest issuance has succeeded this field will be unset. + type: string + format: date-time + nextPrivateKeySecretName: + description: The name of the Secret resource containing the private key to be used for the next certificate iteration. The keymanager controller will automatically set this field if the `Issuing` condition is set to `True`. It will automatically unset this field when the Issuing condition is not set or False. + type: string + notAfter: + description: The expiration time of the certificate stored in the secret named by this resource in `spec.secretName`. + type: string + format: date-time + notBefore: + description: The time after which the certificate stored in the secret named by this resource in `spec.secretName` is valid. + type: string + format: date-time + renewalTime: + description: RenewalTime is the time at which the certificate will be next renewed. If not set, no upcoming renewal is scheduled. + type: string + format: date-time + revision: + description: "The current 'revision' of the certificate as issued. \n When a CertificateRequest resource is created, it will have the `cert-manager.io/certificate-revision` set to one greater than the current value of this field. \n Upon issuance, this field will be set to the value of the annotation on the CertificateRequest resource used to issue the certificate. \n Persisting the value on the CertificateRequest resource allows the certificates controller to know whether a request is part of an old issuance or if it is part of the ongoing revision's issuance by checking if the revision value in the annotation is greater than this field." + type: integer + served: true + storage: true +--- +# Source: cert-manager/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: challenges.acme.cert-manager.io + labels: + app: 'cert-manager' + app.kubernetes.io/name: 'cert-manager' + app.kubernetes.io/instance: 'cert-manager' + # Generated labels + app.kubernetes.io/version: "v1.13.1" +spec: + group: acme.cert-manager.io + names: + kind: Challenge + listKind: ChallengeList + plural: challenges + singular: challenge + categories: + - cert-manager + - cert-manager-acme + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.dnsName + name: Domain + type: string + - jsonPath: .status.reason + name: Reason + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Challenge is a type to represent a Challenge request with an ACME server + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + type: object + required: + - authorizationURL + - dnsName + - issuerRef + - key + - solver + - token + - type + - url + properties: + authorizationURL: + description: The URL to the ACME Authorization resource that this challenge is a part of. + type: string + dnsName: + description: dnsName is the identifier that this challenge is for, e.g. example.com. If the requested DNSName is a 'wildcard', this field MUST be set to the non-wildcard domain, e.g. for `*.example.com`, it must be `example.com`. + type: string + issuerRef: + description: References a properly configured ACME-type Issuer which should be used to create this Challenge. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Challenge will be marked as failed. + type: object + required: + - name + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + key: + description: 'The ACME challenge key for this challenge For HTTP01 challenges, this is the value that must be responded with to complete the HTTP01 challenge in the format: `.`. For DNS01 challenges, this is the base64 encoded SHA256 sum of the `.` text that must be set as the TXT record content.' + type: string + solver: + description: Contains the domain solving configuration that should be used to solve this challenge resource. + type: object + properties: + dns01: + description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. + type: object + properties: + acmeDNS: + description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. + type: object + required: + - accountSecretRef + - host + properties: + accountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + host: + type: string + akamai: + description: Use the Akamai DNS zone management API to manage DNS01 challenge records. + type: object + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + properties: + accessTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientSecretSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + serviceConsumerDomain: + type: string + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. + type: object + required: + - resourceGroupName + - subscriptionID + properties: + clientID: + description: if both this and ClientSecret are left unset MSI will be used + type: string + clientSecretSecretRef: + description: if both this and ClientID are left unset MSI will be used + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + environment: + description: name of the Azure environment (default AzurePublicCloud) + type: string + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID + type: object + properties: + clientID: + description: client ID of the managed identity, can not be used at the same time as resourceID + type: string + resourceID: + description: resource ID of the managed identity, can not be used at the same time as clientID + type: string + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: when specifying ClientID and ClientSecret then this field is also needed + type: string + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 challenge records. + type: object + required: + - project + properties: + hostedZoneName: + description: HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone. + type: string + project: + type: string + serviceAccountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge records. + type: object + properties: + apiKeySecretRef: + description: 'API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + email: + description: Email of the account, only required when using API key based authentication. + type: string + cnameStrategy: + description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. + type: string + enum: + - None + - Follow + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 challenge records. + type: object + required: + - tokenSecretRef + properties: + tokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + rfc2136: + description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. + type: object + required: + - nameserver + properties: + nameserver: + description: The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required. + type: string + tsigAlgorithm: + description: 'The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when ``tsigSecretSecretRef`` and ``tsigKeyName`` are defined. Supported values are (case-insensitive): ``HMACMD5`` (default), ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``.' + type: string + tsigKeyName: + description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + route53: + description: Use the AWS Route53 API to manage DNS01 challenge records. + type: object + required: + - region + properties: + accessKeyID: + description: 'The AccessKeyID is used for authentication. Cannot be set when SecretAccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: string + accessKeyIDSecretRef: + description: 'The SecretAccessKey is used for authentication. If set, pull the AWS access key ID from a key within a Kubernetes Secret. Cannot be set when AccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + hostedZoneID: + description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. + type: string + region: + description: Always set the region when using AccessKeyID and SecretAccessKey + type: string + role: + description: Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata + type: string + secretAccessKeySecretRef: + description: 'The SecretAccessKey is used for authentication. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + webhook: + description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. + type: object + required: + - groupName + - solverName + properties: + config: + description: Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation's documentation. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation. + type: string + solverName: + description: The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. 'cloudflare'. + type: string + http01: + description: Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. `*.example.com`) using the HTTP01 challenge mechanism. + type: object + properties: + gatewayHTTPRoute: + description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future. + type: object + properties: + labels: + description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. + type: object + additionalProperties: type: string - enum: - - None - - Follow - digitalocean: - description: Use the DigitalOcean DNS API to manage DNS01 challenge records. - type: object - required: - - tokenSecretRef - properties: - tokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - rfc2136: - description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. - type: object - required: - - nameserver - properties: - nameserver: - description: The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required. - type: string - tsigAlgorithm: - description: 'The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when ``tsigSecretSecretRef`` and ``tsigKeyName`` are defined. Supported values are (case-insensitive): ``HMACMD5`` (default), ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``.' - type: string - tsigKeyName: - description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. - type: string - tsigSecretSecretRef: - description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - route53: - description: Use the AWS Route53 API to manage DNS01 challenge records. - type: object - required: - - region - properties: - accessKeyID: - description: 'The AccessKeyID is used for authentication. Cannot be set when SecretAccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' - type: string - accessKeyIDSecretRef: - description: 'The SecretAccessKey is used for authentication. If set, pull the AWS access key ID from a key within a Kubernetes Secret. Cannot be set when AccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - hostedZoneID: - description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. - type: string - region: - description: Always set the region when using AccessKeyID and SecretAccessKey - type: string - role: - description: Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata - type: string - secretAccessKeySecretRef: - description: 'The SecretAccessKey is used for authentication. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - webhook: - description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. + parentRefs: + description: 'When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: https://gateway-api.sigs.k8s.io/api-types/httproute/#attaching-to-gateways' + type: array + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." type: object required: - - groupName - - solverName + - name properties: - config: - description: Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation's documentation. - x-kubernetes-preserve-unknown-fields: true - groupName: - description: The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation. + group: + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" type: string - solverName: - description: The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. 'cloudflare'. + default: gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + kind: + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." type: string - http01: - description: Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. `*.example.com`) using the HTTP01 challenge mechanism. - type: object - properties: - gatewayHTTPRoute: - description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future. - type: object - properties: - labels: - description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. - type: object - additionalProperties: - type: string - parentRefs: - description: 'When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: https://gateway-api.sigs.k8s.io/api-types/httproute/#attaching-to-gateways' - type: array - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - type: object - required: - - name - properties: - group: - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - type: string - default: gateway.networking.k8s.io - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - kind: - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - type: string - default: Gateway - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - name: - description: "Name is the name of the referent. \n Support: Core" - type: string - maxLength: 253 - minLength: 1 - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - type: string - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - type: integer - format: int32 - maximum: 65535 - minimum: 1 - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - type: string - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - serviceType: - description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + default: Gateway + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + name: + description: "Name is the name of the referent. \n Support: Core" type: string - ingress: - description: The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are provisioned by cert-manager for each Challenge to be completed. - type: object - properties: - class: - description: The ingress class to use when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of 'class' or 'name' may be specified. + maxLength: 253 + minLength: 1 + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" type: string - ingressTemplate: - description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. - type: object - properties: - metadata: - description: ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. - type: object - properties: - annotations: - description: Annotations that should be added to the created ACME HTTP01 solver ingress. - type: object - additionalProperties: - type: string - labels: - description: Labels that should be added to the created ACME HTTP01 solver ingress. - type: object - additionalProperties: - type: string - name: - description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + type: integer + format: int32 + maximum: 65535 + minimum: 1 + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" type: string - podTemplate: - description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. - type: object - properties: - metadata: - description: ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. - type: object - properties: - annotations: - description: Annotations that should be added to the create ACME HTTP01 solver pods. - type: object - additionalProperties: - type: string - labels: - description: Labels that should be added to the created ACME HTTP01 solver pods. - type: object - additionalProperties: - type: string - spec: - description: PodSpec defines overrides for the HTTP01 challenge solver pod. Only the 'priorityClassName', 'nodeSelector', 'affinity', 'serviceAccountName' and 'tolerations' fields are supported currently. All other fields will be ignored. - type: object - properties: - affinity: - description: If specified, the pod's scheduling constraints - type: object - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for the pod. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + ingress: + description: The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are provisioned by cert-manager for each Challenge to be completed. + type: object + properties: + class: + description: This field configures the annotation `kubernetes.io/ingress.class` when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of `class`, `name` or `ingressClassName` may be specified. + type: string + ingressClassName: + description: This field configures the field `ingressClassName` on the created Ingress resources used to solve ACME challenges that use this challenge solver. This is the recommended way of configuring the ingress class. Only one of `class`, `name` or `ingressClassName` may be specified. + type: string + ingressTemplate: + description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + name: + description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. Only one of `class`, `name` or `ingressClassName` may be specified. + type: string + podTemplate: + description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the create ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + spec: + description: PodSpec defines overrides for the HTTP01 challenge solver pod. Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. All other fields will be ignored. + type: object + properties: + affinity: + description: If specified, the pod's scheduling constraints + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). type: object + required: + - preference + - weight properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. - type: array - items: - description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - type: object - required: - - preference - - weight - properties: - preference: - description: A node selector term, associated with the corresponding weight. - type: object - properties: - matchExpressions: - description: A list of node selector requirements by node's labels. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchFields: - description: A list of node selector requirements by node's fields. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + preference: + description: A node selector term, associated with the corresponding weight. type: object - required: - - nodeSelectorTerms properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The terms are ORed. + matchExpressions: + description: A list of node selector requirements by node's labels. type: array items: - description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. - type: object - properties: - matchExpressions: - description: A list of node selector requirements by node's labels. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchFields: - description: A list of node selector requirements by node's fields. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - x-kubernetes-map-type: atomic - x-kubernetes-map-type: atomic - podAffinity: - description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. - type: array - items: - description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) - type: object - required: - - podAffinityTerm - - weight - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated with the corresponding weight. + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. type: object required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. type: array items: type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - weight: - description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. - type: array - items: - description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. type: object + required: + - key + - operator properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. type: array items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + type: object + required: + - nodeSelectorTerms + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + type: array + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. type: object + required: + - key + - operator properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. type: array items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. - type: array - items: - description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) - type: object - required: - - podAffinityTerm - - weight - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated with the corresponding weight. + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. type: object required: - - topologyKey + - key + - operator properties: - labelSelector: - description: A label query over a set of resources, in this case pods. + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + x-kubernetes-map-type: atomic + x-kubernetes-map-type: atomic + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. type: object + required: + - key + - operator properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. type: array items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. type: object + required: + - key + - operator properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. type: array items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: type: string - weight: - description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. - type: array - items: - description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. type: object + required: + - key + - operator properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. type: array items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. type: object + required: + - key + - operator properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. type: array items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: type: string - nodeSelector: - description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' - type: object - additionalProperties: - type: string - priorityClassName: - description: If specified, the pod's priorityClassName. - type: string - serviceAccountName: - description: If specified, the pod's service account - type: string - tolerations: - description: If specified, the pod's tolerations. - type: array - items: - description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . - type: object - properties: - effect: - description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. - type: integer - format: int64 - value: - description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string - serviceType: - description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. - type: string - selector: - description: Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the 'default' solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead. - type: object - properties: - dnsNames: - description: List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. - type: array - items: - type: string - dnsZones: - description: List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. - type: array - items: - type: string - matchLabels: - description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. - type: object - additionalProperties: - type: string - ca: - description: CA configures this issuer to sign certificates using a signing CA keypair stored in a Secret resource. This is used to build internal PKIs that are managed by cert-manager. - type: object - required: - - secretName - properties: - crlDistributionPoints: - description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set, certificates will be issued without distribution points set. - type: array - items: - type: string - ocspServers: - description: The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". - type: array - items: - type: string - secretName: - description: SecretName is the name of the secret used to sign Certificates issued by this Issuer. - type: string - selfSigned: - description: SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object. - type: object - properties: - crlDistributionPoints: - description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set certificate will be issued without CDP. Values are strings. - type: array - items: - type: string - vault: - description: Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend. - type: object - required: - - auth - - path - - server - properties: - auth: - description: Auth configures how cert-manager authenticates with the Vault server. - type: object - properties: - appRole: - description: AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource. - type: object - required: - - path - - roleId - - secretRef - properties: - path: - description: 'Path where the App Role authentication backend is mounted in Vault, e.g: "approle"' - type: string - roleId: - description: RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault. - type: string - secretRef: - description: Reference to a key in a Secret that contains the App Role secret used to authenticate with Vault. The `key` field must be specified and denotes which entry within the Secret resource is used as the app role secret. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - kubernetes: - description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server. - type: object - required: - - role - - secretRef - properties: - mountPath: - description: The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to `/v1/auth/foo`, will use the path `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the default value "/v1/auth/kubernetes" will be used. - type: string - role: - description: A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies. - type: string - secretRef: - description: The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of 'ambient credentials' is not supported. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - tokenSecretRef: - description: TokenSecretRef authenticates with Vault by presenting a token. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - caBundle: - description: Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by Vault. Only used if using HTTPS to connect to Vault and ignored for HTTP connections. Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection. - type: string - format: byte - caBundleSecretRef: - description: Reference to a Secret containing a bundle of PEM-encoded CAs to use when verifying the certificate chain presented by Vault when using HTTPS. Mutually exclusive with CABundle. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection. If no key for the Secret is specified, cert-manager will default to 'ca.crt'. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Name of the vault namespace. Namespaces is a set of features within Vault Enterprise that allows Vault environments to support Secure Multi-tenancy. e.g: "ns1" More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces' - type: string - path: - description: 'Path is the mount path of the Vault PKI backend''s `sign` endpoint, e.g: "my_pki_mount/sign/my-role-name".' - type: string - server: - description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' - type: string - venafi: - description: Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone. - type: object - required: - - zone - properties: - cloud: - description: Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified. - type: object - required: - - apiTokenSecretRef - properties: - apiTokenSecretRef: - description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + type: array + items: + description: LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + x-kubernetes-map-type: atomic + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. type: string - url: - description: URL is the base URL for Venafi Cloud. Defaults to "https://api.venafi.cloud/v1". - type: string - tpp: - description: TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified. + selector: + description: Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the 'default' solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead. type: object - required: - - credentialsRef - - url properties: - caBundle: - description: Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by the TPP server. Only used if using HTTPS; ignored for HTTP. If undefined, the certificate bundle in the cert-manager controller container is used to validate the chain. - type: string - format: byte - credentialsRef: - description: CredentialsRef is a reference to a Secret containing the username and password for the TPP server. The secret must contain two keys, 'username' and 'password'. + dnsNames: + description: List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + dnsZones: + description: List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + matchLabels: + description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. type: object - required: - - name - properties: - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - url: - description: 'URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: "https://tpp.example.com/vedsdk".' - type: string - zone: - description: Zone is the Venafi Policy Zone to use for this issuer. All requests made to the Venafi platform will be restricted by the named zone policy. This field is required. - type: string + additionalProperties: + type: string + token: + description: The ACME challenge token for this challenge. This is the raw value returned from the ACME server. + type: string + type: + description: The type of ACME challenge this resource represents. One of "HTTP-01" or "DNS-01". + type: string + enum: + - HTTP-01 + - DNS-01 + url: + description: The URL of the ACME Challenge resource for this challenge. This can be used to lookup details about the status of this challenge. + type: string + wildcard: + description: wildcard will be true if this challenge is for a wildcard identifier, for example '*.example.com'. + type: boolean status: - description: Status of the ClusterIssuer. This is set and managed automatically. type: object properties: - acme: - description: ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates. - type: object - properties: - lastRegisteredEmail: - description: LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer - type: string - uri: - description: URI is the unique account identifier, which can also be used to retrieve account details from the CA - type: string - conditions: - description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`. - type: array - items: - description: IssuerCondition contains condition information for an Issuer. - type: object - required: - - status - - type - properties: - lastTransitionTime: - description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. - type: string - format: date-time - message: - description: Message is a human readable description of the details of the last transition, complementing reason. - type: string - observedGeneration: - description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Issuer. - type: integer - format: int64 - reason: - description: Reason is a brief machine readable explanation for the condition's last transition. - type: string - status: - description: Status of the condition, one of (`True`, `False`, `Unknown`). - type: string - enum: - - "True" - - "False" - - Unknown - type: - description: Type of the condition, known values are (`Ready`). - type: string - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map + presented: + description: presented will be set to true if the challenge values for this challenge are currently 'presented'. This *does not* imply the self check is passing. Only that the values have been 'submitted' for the appropriate challenge mechanism (i.e. the DNS01 TXT record has been presented, or the HTTP01 configuration has been configured). + type: boolean + processing: + description: Used to denote whether this challenge should be processed or not. This field will only be set to true by the 'scheduling' component. It will only be set to false by the 'challenges' controller, after the challenge has reached a final state or timed out. If this field is set to false, the challenge controller will not take any more action. + type: boolean + reason: + description: Contains human readable information on why the Challenge is in the current state. + type: string + state: + description: Contains the current 'state' of the challenge. If not set, the state of the challenge is unknown. + type: string + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored served: true storage: true + subresources: + status: {} --- # Source: cert-manager/templates/crds.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: challenges.acme.cert-manager.io + name: clusterissuers.cert-manager.io labels: app: 'cert-manager' app.kubernetes.io/name: 'cert-manager' - app.kubernetes.io/instance: 'cert-manager' + app.kubernetes.io/instance: "cert-manager" # Generated labels - app.kubernetes.io/version: "v1.11.0" + app.kubernetes.io/version: "v1.13.1" spec: - group: acme.cert-manager.io + group: cert-manager.io names: - kind: Challenge - listKind: ChallengeList - plural: challenges - singular: challenge - categories: - - cert-manager - - cert-manager-acme - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.state - name: State - type: string - - jsonPath: .spec.dnsName - name: Domain + kind: ClusterIssuer + listKind: ClusterIssuerList + plural: clusterissuers + singular: clusterissuer + categories: + - cert-manager + scope: Cluster + versions: + - name: v1 + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready type: string - - jsonPath: .status.reason - name: Reason + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status priority: 1 type: string - - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. - jsonPath: .metadata.creationTimestamp + - jsonPath: .metadata.creationTimestamp + description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. name: Age type: date - name: v1 schema: openAPIV3Schema: - description: Challenge is a type to represent a Challenge request with an ACME server + description: A ClusterIssuer represents a certificate issuing authority which can be referenced as part of `issuerRef` fields. It is similar to an Issuer, however it is cluster-scoped and therefore can be referenced by resources that exist in *any* namespace, not just the same namespace as the referent. type: object required: - - metadata - spec properties: apiVersion: @@ -1363,1179 +1712,1238 @@ spec: metadata: type: object spec: + description: Desired state of the ClusterIssuer resource. type: object - required: - - authorizationURL - - dnsName - - issuerRef - - key - - solver - - token - - type - - url properties: - authorizationURL: - description: The URL to the ACME Authorization resource that this challenge is a part of. - type: string - dnsName: - description: dnsName is the identifier that this challenge is for, e.g. example.com. If the requested DNSName is a 'wildcard', this field MUST be set to the non-wildcard domain, e.g. for `*.example.com`, it must be `example.com`. - type: string - issuerRef: - description: References a properly configured ACME-type Issuer which should be used to create this Challenge. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Challenge will be marked as failed. + acme: + description: ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates. type: object required: - - name + - privateKeySecretRef + - server properties: - group: - description: Group of the resource being referred to. - type: string - kind: - description: Kind of the resource being referred to. + caBundle: + description: Base64-encoded bundle of PEM CAs which can be used to validate the certificate chain presented by the ACME server. Mutually exclusive with SkipTLSVerify; prefer using CABundle to prevent various kinds of security vulnerabilities. If CABundle and SkipTLSVerify are unset, the system certificate bundle inside the container is used to validate the TLS connection. type: string - name: - description: Name of the resource being referred to. + format: byte + disableAccountKeyGeneration: + description: Enables or disables generating a new ACME account key. If true, the Issuer resource will *not* request a new account but will expect the account key to be supplied via an existing secret. If false, the cert-manager system will generate a new ACME account key for the Issuer. Defaults to false. + type: boolean + email: + description: Email is the email address to be associated with the ACME account. This field is optional, but it is strongly recommended to be set. It will be used to contact you in case of issues with your account or certificates, including expiry notification emails. This field may be updated after the account is initially registered. type: string - key: - description: 'The ACME challenge key for this challenge For HTTP01 challenges, this is the value that must be responded with to complete the HTTP01 challenge in the format: `.`. For DNS01 challenges, this is the base64 encoded SHA256 sum of the `.` text that must be set as the TXT record content.' - type: string - solver: - description: Contains the domain solving configuration that should be used to solve this challenge resource. - type: object - properties: - dns01: - description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. + enableDurationFeature: + description: Enables requesting a Not After date on certificates that matches the duration of the certificate. This is not supported by all ACME servers like Let's Encrypt. If set to true when the ACME server does not support it it will create an error on the Order. Defaults to false. + type: boolean + externalAccountBinding: + description: ExternalAccountBinding is a reference to a CA external account of the ACME server. If set, upon registration cert-manager will attempt to associate the given external account credentials with the registered ACME account. type: object + required: + - keyID + - keySecretRef properties: - acmeDNS: - description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. - type: object - required: - - accountSecretRef - - host - properties: - accountSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - host: - type: string - akamai: - description: Use the Akamai DNS zone management API to manage DNS01 challenge records. - type: object - required: - - accessTokenSecretRef - - clientSecretSecretRef - - clientTokenSecretRef - - serviceConsumerDomain - properties: - accessTokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - clientSecretSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - clientTokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - serviceConsumerDomain: - type: string - azureDNS: - description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. - type: object - required: - - resourceGroupName - - subscriptionID - properties: - clientID: - description: if both this and ClientSecret are left unset MSI will be used - type: string - clientSecretSecretRef: - description: if both this and ClientID are left unset MSI will be used - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - environment: - description: name of the Azure environment (default AzurePublicCloud) - type: string - enum: - - AzurePublicCloud - - AzureChinaCloud - - AzureGermanCloud - - AzureUSGovernmentCloud - hostedZoneName: - description: name of the DNS zone that should be used - type: string - managedIdentity: - description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID - type: object - properties: - clientID: - description: client ID of the managed identity, can not be used at the same time as resourceID - type: string - resourceID: - description: resource ID of the managed identity, can not be used at the same time as clientID - type: string - resourceGroupName: - description: resource group the DNS zone is located in - type: string - subscriptionID: - description: ID of the Azure subscription - type: string - tenantID: - description: when specifying ClientID and ClientSecret then this field is also needed - type: string - cloudDNS: - description: Use the Google Cloud DNS API to manage DNS01 challenge records. - type: object - required: - - project - properties: - hostedZoneName: - description: HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone. - type: string - project: - type: string - serviceAccountSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - cloudflare: - description: Use the Cloudflare API to manage DNS01 challenge records. - type: object - properties: - apiKeySecretRef: - description: 'API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.' - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - apiTokenSecretRef: - description: API token used to authenticate with Cloudflare. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - email: - description: Email of the account, only required when using API key based authentication. - type: string - cnameStrategy: - description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. + keyAlgorithm: + description: 'Deprecated: keyAlgorithm field exists for historical compatibility reasons and should not be used. The algorithm is now hardcoded to HS256 in golang/x/crypto/acme.' type: string enum: - - None - - Follow - digitalocean: - description: Use the DigitalOcean DNS API to manage DNS01 challenge records. - type: object - required: - - tokenSecretRef - properties: - tokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - rfc2136: - description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. - type: object - required: - - nameserver - properties: - nameserver: - description: The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required. - type: string - tsigAlgorithm: - description: 'The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when ``tsigSecretSecretRef`` and ``tsigKeyName`` are defined. Supported values are (case-insensitive): ``HMACMD5`` (default), ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``.' - type: string - tsigKeyName: - description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. - type: string - tsigSecretSecretRef: - description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - route53: - description: Use the AWS Route53 API to manage DNS01 challenge records. - type: object - required: - - region - properties: - accessKeyID: - description: 'The AccessKeyID is used for authentication. Cannot be set when SecretAccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' - type: string - accessKeyIDSecretRef: - description: 'The SecretAccessKey is used for authentication. If set, pull the AWS access key ID from a key within a Kubernetes Secret. Cannot be set when AccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - hostedZoneID: - description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. - type: string - region: - description: Always set the region when using AccessKeyID and SecretAccessKey - type: string - role: - description: Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata - type: string - secretAccessKeySecretRef: - description: 'The SecretAccessKey is used for authentication. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - webhook: - description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. + - HS256 + - HS384 + - HS512 + keyID: + description: keyID is the ID of the CA key that the External Account is bound to. + type: string + keySecretRef: + description: keySecretRef is a Secret Key Selector referencing a data item in a Kubernetes Secret which holds the symmetric MAC key of the External Account Binding. The `key` is the index string that is paired with the key data in the Secret and should not be confused with the key data itself, or indeed with the External Account Binding keyID above. The secret key stored in the Secret **must** be un-padded, base64 URL encoded data. type: object required: - - groupName - - solverName + - name properties: - config: - description: Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation's documentation. - x-kubernetes-preserve-unknown-fields: true - groupName: - description: The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation. + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. type: string - solverName: - description: The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. 'cloudflare'. + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' type: string - http01: - description: Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. `*.example.com`) using the HTTP01 challenge mechanism. + preferredChain: + description: 'PreferredChain is the chain to use if the ACME server outputs multiple. PreferredChain is no guarantee that this one gets delivered by the ACME endpoint. For example, for Let''s Encrypt''s DST crosssign you would use: "DST Root CA X3" or "ISRG Root X1" for the newer Let''s Encrypt root CA. This value picks the first certificate bundle in the ACME alternative chains that has a certificate with this value as its issuer''s CN' + type: string + maxLength: 64 + privateKeySecretRef: + description: PrivateKey is the name of a Kubernetes Secret resource that will be used to store the automatically generated ACME account private key. Optionally, a `key` may be specified to select a specific entry within the named Secret resource. If `key` is not specified, a default of `tls.key` will be used. type: object + required: + - name properties: - gatewayHTTPRoute: - description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future. - type: object - properties: - labels: - description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. - type: object - additionalProperties: - type: string - parentRefs: - description: 'When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: https://gateway-api.sigs.k8s.io/api-types/httproute/#attaching-to-gateways' - type: array - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + server: + description: 'Server is the URL used to access the ACME server''s ''directory'' endpoint. For example, for Let''s Encrypt''s staging endpoint, you would use: "https://acme-staging-v02.api.letsencrypt.org/directory". Only ACME v2 endpoints (i.e. RFC 8555) are supported.' + type: string + skipTLSVerify: + description: 'INSECURE: Enables or disables validation of the ACME server TLS certificate. If true, requests to the ACME server will not have the TLS certificate chain validated. Mutually exclusive with CABundle; prefer using CABundle to prevent various kinds of security vulnerabilities. Only enable this option in development environments. If CABundle and SkipTLSVerify are unset, the system certificate bundle inside the container is used to validate the TLS connection. Defaults to false.' + type: boolean + solvers: + description: 'Solvers is a list of challenge solvers that will be used to solve ACME challenges for the matching domains. Solver configurations must be provided in order to obtain certificates from an ACME server. For more information, see: https://cert-manager.io/docs/configuration/acme/' + type: array + items: + description: An ACMEChallengeSolver describes how to solve ACME challenges for the issuer it is part of. A selector may be provided to use different solving strategies for different DNS names. Only one of HTTP01 or DNS01 must be provided. + type: object + properties: + dns01: + description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. + type: object + properties: + acmeDNS: + description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. + type: object + required: + - accountSecretRef + - host + properties: + accountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + host: + type: string + akamai: + description: Use the Akamai DNS zone management API to manage DNS01 challenge records. + type: object + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + properties: + accessTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientSecretSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + clientTokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + serviceConsumerDomain: + type: string + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. + type: object + required: + - resourceGroupName + - subscriptionID + properties: + clientID: + description: if both this and ClientSecret are left unset MSI will be used + type: string + clientSecretSecretRef: + description: if both this and ClientID are left unset MSI will be used + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + environment: + description: name of the Azure environment (default AzurePublicCloud) + type: string + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID + type: object + properties: + clientID: + description: client ID of the managed identity, can not be used at the same time as resourceID + type: string + resourceID: + description: resource ID of the managed identity, can not be used at the same time as clientID + type: string + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: when specifying ClientID and ClientSecret then this field is also needed + type: string + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 challenge records. type: object required: - - name + - project properties: - group: - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + hostedZoneName: + description: HostedZoneName is an optional field that tells cert-manager in which Cloud DNS zone the challenge record has to be created. If left empty cert-manager will automatically choose a zone. type: string - default: gateway.networking.k8s.io - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - kind: - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + project: type: string - default: Gateway - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - name: - description: "Name is the name of the referent. \n Support: Core" + serviceAccountSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge records. + type: object + properties: + apiKeySecretRef: + description: 'API key to use to authenticate with Cloudflare. Note: using an API token to authenticate is now the recommended method as it allows greater control of permissions.' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + email: + description: Email of the account, only required when using API key based authentication. type: string - maxLength: 253 - minLength: 1 - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + cnameStrategy: + description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. + type: string + enum: + - None + - Follow + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 challenge records. + type: object + required: + - tokenSecretRef + properties: + tokenSecretRef: + description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + rfc2136: + description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. + type: object + required: + - nameserver + properties: + nameserver: + description: The IP address or hostname of an authoritative DNS server supporting RFC2136 in the form host:port. If the host is an IPv6 address it must be enclosed in square brackets (e.g [2001:db8::1]) ; port is optional. This field is required. type: string - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - type: integer - format: int32 - maximum: 65535 - minimum: 1 - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + tsigAlgorithm: + description: 'The TSIG Algorithm configured in the DNS supporting RFC2136. Used only when ``tsigSecretSecretRef`` and ``tsigKeyName`` are defined. Supported values are (case-insensitive): ``HMACMD5`` (default), ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``.' type: string - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - serviceType: - description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. - type: string - ingress: - description: The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are provisioned by cert-manager for each Challenge to be completed. - type: object - properties: - class: - description: The ingress class to use when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of 'class' or 'name' may be specified. - type: string - ingressTemplate: - description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. - type: object - properties: - metadata: - description: ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. - type: object - properties: - annotations: - description: Annotations that should be added to the created ACME HTTP01 solver ingress. - type: object - additionalProperties: + tsigKeyName: + description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. type: string - labels: - description: Labels that should be added to the created ACME HTTP01 solver ingress. - type: object - additionalProperties: + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' type: string - name: - description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. - type: string - podTemplate: - description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. - type: object - properties: - metadata: - description: ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. - type: object - properties: - annotations: - description: Annotations that should be added to the create ACME HTTP01 solver pods. - type: object - additionalProperties: + route53: + description: Use the AWS Route53 API to manage DNS01 challenge records. + type: object + required: + - region + properties: + accessKeyID: + description: 'The AccessKeyID is used for authentication. Cannot be set when SecretAccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: string + accessKeyIDSecretRef: + description: 'The SecretAccessKey is used for authentication. If set, pull the AWS access key ID from a key within a Kubernetes Secret. Cannot be set when AccessKeyID is set. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. type: string - labels: - description: Labels that should be added to the created ACME HTTP01 solver pods. - type: object - additionalProperties: + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' type: string - spec: - description: PodSpec defines overrides for the HTTP01 challenge solver pod. Only the 'priorityClassName', 'nodeSelector', 'affinity', 'serviceAccountName' and 'tolerations' fields are supported currently. All other fields will be ignored. - type: object - properties: - affinity: - description: If specified, the pod's scheduling constraints + hostedZoneID: + description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. + type: string + region: + description: Always set the region when using AccessKeyID and SecretAccessKey + type: string + role: + description: Role is a Role ARN which the Route53 provider will assume using either the explicit credentials AccessKeyID/SecretAccessKey or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata + type: string + secretAccessKeySecretRef: + description: 'The SecretAccessKey is used for authentication. If neither the Access Key nor Key ID are set, we fall-back to using env vars, shared credentials file or AWS Instance metadata, see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials' + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + webhook: + description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. + type: object + required: + - groupName + - solverName + properties: + config: + description: Additional configuration that should be passed to the webhook apiserver when challenges are processed. This can contain arbitrary JSON data. Secret values should not be specified in this stanza. If secret values are needed (e.g. credentials for a DNS service), you should use a SecretKeySelector to reference a Secret resource. For details on the schema of this field, consult the webhook provider implementation's documentation. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: The API group name that should be used when POSTing ChallengePayload resources to the webhook apiserver. This should be the same as the GroupName specified in the webhook provider implementation. + type: string + solverName: + description: The name of the solver to use, as defined in the webhook provider implementation. This will typically be the name of the provider, e.g. 'cloudflare'. + type: string + http01: + description: Configures cert-manager to attempt to complete authorizations by performing the HTTP01 challenge flow. It is not possible to obtain certificates for wildcard domain names (e.g. `*.example.com`) using the HTTP01 challenge mechanism. + type: object + properties: + gatewayHTTPRoute: + description: The Gateway API is a sig-network community API that models service networking in Kubernetes (https://gateway-api.sigs.k8s.io/). The Gateway solver will create HTTPRoutes with the specified labels in the same namespace as the challenge. This solver is experimental, and fields / behaviour may change in the future. + type: object + properties: + labels: + description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. + type: object + additionalProperties: + type: string + parentRefs: + description: 'When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: https://gateway-api.sigs.k8s.io/api-types/httproute/#attaching-to-gateways' + type: array + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." type: object + required: + - name properties: - nodeAffinity: - description: Describes node affinity scheduling rules for the pod. - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. - type: array - items: - description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + group: + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + type: string + default: gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + kind: + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + type: string + default: Gateway + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + name: + description: "Name is the name of the referent. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + type: string + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + type: integer + format: int32 + maximum: 65535 + minimum: 1 + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + type: string + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + ingress: + description: The ingress based HTTP01 challenge solver will solve challenges by creating or modifying Ingress resources in order to route requests for '/.well-known/acme-challenge/XYZ' to 'challenge solver' pods that are provisioned by cert-manager for each Challenge to be completed. + type: object + properties: + class: + description: This field configures the annotation `kubernetes.io/ingress.class` when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of `class`, `name` or `ingressClassName` may be specified. + type: string + ingressClassName: + description: This field configures the field `ingressClassName` on the created Ingress resources used to solve ACME challenges that use this challenge solver. This is the recommended way of configuring the ingress class. Only one of `class`, `name` or `ingressClassName` may be specified. + type: string + ingressTemplate: + description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the ingress used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver ingress. + type: object + additionalProperties: + type: string + name: + description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. Only one of `class`, `name` or `ingressClassName` may be specified. + type: string + podTemplate: + description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. + type: object + properties: + metadata: + description: ObjectMeta overrides for the pod used to solve HTTP01 challenges. Only the 'labels' and 'annotations' fields may be set. If labels or annotations overlap with in-built values, the values here will override the in-built values. + type: object + properties: + annotations: + description: Annotations that should be added to the create ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + labels: + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + additionalProperties: + type: string + spec: + description: PodSpec defines overrides for the HTTP01 challenge solver pod. Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. All other fields will be ignored. + type: object + properties: + affinity: + description: If specified, the pod's scheduling constraints + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. type: object - required: - - preference - - weight properties: - preference: - description: A node selector term, associated with the corresponding weight. - type: object - properties: - matchExpressions: - description: A list of node selector requirements by node's labels. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + type: object + required: + - preference + - weight + properties: + preference: + description: A node selector term, associated with the corresponding weight. type: object - required: - - key - - operator properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + matchExpressions: + description: A list of node selector requirements by node's labels. type: array items: - type: string - matchFields: - description: A list of node selector requirements by node's fields. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. type: array items: - type: string - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. - type: object - required: - - nodeSelectorTerms - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The terms are ORed. - type: array - items: - description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. type: object + required: + - nodeSelectorTerms properties: - matchExpressions: - description: A list of node selector requirements by node's labels. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchFields: - description: A list of node selector requirements by node's fields. + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. type: array items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. type: object - required: - - key - - operator properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + matchExpressions: + description: A list of node selector requirements by node's labels. type: array items: - type: string + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + x-kubernetes-map-type: atomic x-kubernetes-map-type: atomic - x-kubernetes-map-type: atomic - podAffinity: - description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. - type: array - items: - description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). type: object - required: - - podAffinityTerm - - weight properties: - podAffinityTerm: - description: Required. A pod affinity term, associated with the corresponding weight. - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. type: object - required: - - key - - operator properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. type: array items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. type: object - required: - - key - - operator properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. type: array items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - weight: - description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. - type: array - items: - description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. type: array items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. type: object - required: - - key - - operator properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. type: array items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. - type: array - items: - description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). type: object - required: - - podAffinityTerm - - weight properties: - podAffinityTerm: - description: Required. A pod affinity term, associated with the corresponding weight. - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. type: object - required: - - key - - operator properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. type: array items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. type: object - required: - - key - - operator properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. type: array items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - weight: - description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. - type: array - items: - description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. type: object - required: - - key - - operator properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. type: array items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + x-kubernetes-map-type: atomic + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. type: object - required: - - key - - operator properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. type: array items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: + x-kubernetes-map-type: atomic + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - nodeSelector: - description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' - type: object - additionalProperties: - type: string - priorityClassName: - description: If specified, the pod's priorityClassName. - type: string - serviceAccountName: - description: If specified, the pod's service account - type: string - tolerations: - description: If specified, the pod's tolerations. - type: array - items: - description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . - type: object - properties: - effect: - description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + type: array + items: + description: LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + x-kubernetes-map-type: atomic + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + priorityClassName: + description: If specified, the pod's priorityClassName. type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. - type: integer - format: int64 - value: - description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + serviceAccountName: + description: If specified, the pod's service account type: string - serviceType: - description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. - type: string - selector: - description: Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the 'default' solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead. + tolerations: + description: If specified, the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + serviceType: + description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + selector: + description: Selector selects a set of DNSNames on the Certificate resource that should be solved using this challenge solver. If not specified, the solver will be treated as the 'default' solver with the lowest priority, i.e. if any other solver has a more specific match, it will be used instead. + type: object + properties: + dnsNames: + description: List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + dnsZones: + description: List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. + type: array + items: + type: string + matchLabels: + description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. + type: object + additionalProperties: + type: string + ca: + description: CA configures this issuer to sign certificates using a signing CA keypair stored in a Secret resource. This is used to build internal PKIs that are managed by cert-manager. + type: object + required: + - secretName + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set, certificates will be issued without distribution points set. + type: array + items: + type: string + ocspServers: + description: The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". + type: array + items: + type: string + secretName: + description: SecretName is the name of the secret used to sign Certificates issued by this Issuer. + type: string + selfSigned: + description: SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object. + type: object + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set certificate will be issued without CDP. Values are strings. + type: array + items: + type: string + vault: + description: Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend. + type: object + required: + - auth + - path + - server + properties: + auth: + description: Auth configures how cert-manager authenticates with the Vault server. type: object properties: - dnsNames: - description: List of DNSNames that this solver will be used to solve. If specified and a match is found, a dnsNames selector will take precedence over a dnsZones selector. If multiple solvers match with the same dnsNames value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. - type: array - items: - type: string - dnsZones: - description: List of DNSZones that this solver will be used to solve. The most specific DNS zone match specified here will take precedence over other DNS zone matches, so a solver specifying sys.example.com will be selected over one specifying example.com for the domain www.sys.example.com. If multiple solvers match with the same dnsZones value, the solver with the most matching labels in matchLabels will be selected. If neither has more matches, the solver defined earlier in the list will be selected. - type: array - items: - type: string - matchLabels: - description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. + appRole: + description: AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource. type: object - additionalProperties: - type: string - token: - description: The ACME challenge token for this challenge. This is the raw value returned from the ACME server. - type: string - type: - description: The type of ACME challenge this resource represents. One of "HTTP-01" or "DNS-01". - type: string - enum: - - HTTP-01 - - DNS-01 - url: - description: The URL of the ACME Challenge resource for this challenge. This can be used to lookup details about the status of this challenge. - type: string - wildcard: - description: wildcard will be true if this challenge is for a wildcard identifier, for example '*.example.com'. - type: boolean - status: - type: object - properties: - presented: - description: presented will be set to true if the challenge values for this challenge are currently 'presented'. This *does not* imply the self check is passing. Only that the values have been 'submitted' for the appropriate challenge mechanism (i.e. the DNS01 TXT record has been presented, or the HTTP01 configuration has been configured). - type: boolean - processing: - description: Used to denote whether this challenge should be processed or not. This field will only be set to true by the 'scheduling' component. It will only be set to false by the 'challenges' controller, after the challenge has reached a final state or timed out. If this field is set to false, the challenge controller will not take any more action. - type: boolean - reason: - description: Contains human readable information on why the Challenge is in the current state. - type: string - state: - description: Contains the current 'state' of the challenge. If not set, the state of the challenge is unknown. - type: string - enum: - - valid - - ready - - pending - - processing - - invalid - - expired - - errored - served: true - storage: true - subresources: - status: {} ---- -# Source: cert-manager/templates/crds.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: certificaterequests.cert-manager.io - labels: - app: 'cert-manager' - app.kubernetes.io/name: 'cert-manager' - app.kubernetes.io/instance: 'cert-manager' - # Generated labels - app.kubernetes.io/version: "v1.11.0" -spec: - group: cert-manager.io - names: - kind: CertificateRequest - listKind: CertificateRequestList - plural: certificaterequests - shortNames: - - cr - - crs - singular: certificaterequest - categories: - - cert-manager - scope: Namespaced - versions: - - name: v1 - subresources: - status: {} - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Approved")].status - name: Approved - type: string - - jsonPath: .status.conditions[?(@.type=="Denied")].status - name: Denied - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .spec.issuerRef.name - name: Issuer - type: string - - jsonPath: .spec.username - name: Requestor - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. - name: Age - type: date - schema: - openAPIV3Schema: - description: "A CertificateRequest is used to request a signed certificate from one of the configured issuers. \n All fields within the CertificateRequest's `spec` are immutable after creation. A CertificateRequest will either succeed or fail, as denoted by its `status.state` field. \n A CertificateRequest is a one-shot resource, meaning it represents a single point in time request for a certificate and cannot be re-used." - type: object - required: - - spec - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Desired state of the CertificateRequest resource. - type: object - required: - - issuerRef - - request - properties: - duration: - description: The requested 'duration' (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types. - type: string - extra: - description: Extra contains extra attributes of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. - type: object - additionalProperties: - type: array - items: + required: + - path + - roleId + - secretRef + properties: + path: + description: 'Path where the App Role authentication backend is mounted in Vault, e.g: "approle"' + type: string + roleId: + description: RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault. + type: string + secretRef: + description: Reference to a key in a Secret that contains the App Role secret used to authenticate with Vault. The `key` field must be specified and denotes which entry within the Secret resource is used as the app role secret. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + kubernetes: + description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server. + type: object + required: + - role + properties: + mountPath: + description: The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to `/v1/auth/foo`, will use the path `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the default value "/v1/auth/kubernetes" will be used. + type: string + role: + description: A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies. + type: string + secretRef: + description: The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of 'ambient credentials' is not supported. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + serviceAccountRef: + description: A reference to a service account that will be used to request a bound token (also known as "projected token"). Compared to using "secretRef", using this field means that you don't rely on statically bound tokens. To use this field, you must configure an RBAC rule to let cert-manager request a token. + type: object + required: + - name + properties: + name: + description: Name of the ServiceAccount used to request a token. + type: string + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + caBundle: + description: Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by Vault. Only used if using HTTPS to connect to Vault and ignored for HTTP connections. Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection. type: string - groups: - description: Groups contains group membership of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. - type: array - items: - type: string - x-kubernetes-list-type: atomic - isCA: - description: IsCA will request to mark the certificate as valid for certificate signing when submitting to the issuer. This will automatically add the `cert sign` usage to the list of `usages`. - type: boolean - issuerRef: - description: IssuerRef is a reference to the issuer for this CertificateRequest. If the `kind` field is not set, or set to `Issuer`, an Issuer resource with the given name in the same namespace as the CertificateRequest will be used. If the `kind` field is set to `ClusterIssuer`, a ClusterIssuer with the provided name will be used. The `name` field in this stanza is required at all times. The group field refers to the API group of the issuer which defaults to `cert-manager.io` if empty. + format: byte + caBundleSecretRef: + description: Reference to a Secret containing a bundle of PEM-encoded CAs to use when verifying the certificate chain presented by Vault when using HTTPS. Mutually exclusive with CABundle. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection. If no key for the Secret is specified, cert-manager will default to 'ca.crt'. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Name of the vault namespace. Namespaces is a set of features within Vault Enterprise that allows Vault environments to support Secure Multi-tenancy. e.g: "ns1" More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces' + type: string + path: + description: 'Path is the mount path of the Vault PKI backend''s `sign` endpoint, e.g: "my_pki_mount/sign/my-role-name".' + type: string + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + venafi: + description: Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone. type: object required: - - name + - zone properties: - group: - description: Group of the resource being referred to. - type: string - kind: - description: Kind of the resource being referred to. - type: string - name: - description: Name of the resource being referred to. + cloud: + description: Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified. + type: object + required: + - apiTokenSecretRef + properties: + apiTokenSecretRef: + description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + url: + description: URL is the base URL for Venafi Cloud. Defaults to "https://api.venafi.cloud/v1". + type: string + tpp: + description: TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified. + type: object + required: + - credentialsRef + - url + properties: + caBundle: + description: Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by the TPP server. Only used if using HTTPS; ignored for HTTP. If undefined, the certificate bundle in the cert-manager controller container is used to validate the chain. + type: string + format: byte + credentialsRef: + description: CredentialsRef is a reference to a Secret containing the username and password for the TPP server. The secret must contain two keys, 'username' and 'password'. + type: object + required: + - name + properties: + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + url: + description: 'URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: "https://tpp.example.com/vedsdk".' + type: string + zone: + description: Zone is the Venafi Policy Zone to use for this issuer. All requests made to the Venafi platform will be restricted by the named zone policy. This field is required. type: string - request: - description: The PEM-encoded x509 certificate signing request to be submitted to the CA for signing. - type: string - format: byte - uid: - description: UID contains the uid of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. - type: string - usages: - description: Usages is the set of x509 usages that are requested for the certificate. If usages are set they SHOULD be encoded inside the CSR spec Defaults to `digital signature` and `key encipherment` if not specified. - type: array - items: - description: "KeyUsage specifies valid usage contexts for keys. See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 https://tools.ietf.org/html/rfc5280#section-4.2.1.12 \n Valid KeyUsage values are as follows: \"signing\", \"digital signature\", \"content commitment\", \"key encipherment\", \"key agreement\", \"data encipherment\", \"cert sign\", \"crl sign\", \"encipher only\", \"decipher only\", \"any\", \"server auth\", \"client auth\", \"code signing\", \"email protection\", \"s/mime\", \"ipsec end system\", \"ipsec tunnel\", \"ipsec user\", \"timestamping\", \"ocsp signing\", \"microsoft sgc\", \"netscape sgc\"" - type: string - enum: - - signing - - digital signature - - content commitment - - key encipherment - - key agreement - - data encipherment - - cert sign - - crl sign - - encipher only - - decipher only - - any - - server auth - - client auth - - code signing - - email protection - - s/mime - - ipsec end system - - ipsec tunnel - - ipsec user - - timestamping - - ocsp signing - - microsoft sgc - - netscape sgc - username: - description: Username contains the name of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. - type: string status: - description: Status of the CertificateRequest. This is set and managed automatically. + description: Status of the ClusterIssuer. This is set and managed automatically. type: object properties: - ca: - description: The PEM encoded x509 certificate of the signer, also known as the CA (Certificate Authority). This is set on a best-effort basis by different issuers. If not set, the CA is assumed to be unknown/not available. - type: string - format: byte - certificate: - description: The PEM encoded x509 certificate resulting from the certificate signing request. If not set, the CertificateRequest has either not been completed or has failed. More information on failure can be found by checking the `conditions` field. - type: string - format: byte + acme: + description: ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates. + type: object + properties: + lastPrivateKeyHash: + description: LastPrivateKeyHash is a hash of the private key associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer + type: string + lastRegisteredEmail: + description: LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer + type: string + uri: + description: URI is the unique account identifier, which can also be used to retrieve account details from the CA + type: string conditions: - description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready` and `InvalidRequest`. + description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`. type: array items: - description: CertificateRequestCondition contains condition information for a CertificateRequest. + description: IssuerCondition contains condition information for an Issuer. type: object required: - status @@ -2548,6 +2956,10 @@ spec: message: description: Message is a human readable description of the details of the last transition, complementing reason. type: string + observedGeneration: + description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Issuer. + type: integer + format: int64 reason: description: Reason is a brief machine readable explanation for the condition's last transition. type: string @@ -2559,15 +2971,11 @@ spec: - "False" - Unknown type: - description: Type of the condition, known values are (`Ready`, `InvalidRequest`, `Approved`, `Denied`). + description: Type of the condition, known values are (`Ready`). type: string x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map - failureTime: - description: FailureTime stores the time that this CertificateRequest failed. This is used to influence garbage collection and back-off. - type: string - format: date-time served: true storage: true --- @@ -2579,9 +2987,9 @@ metadata: labels: app: 'cert-manager' app.kubernetes.io/name: 'cert-manager' - app.kubernetes.io/instance: 'cert-manager' + app.kubernetes.io/instance: "cert-manager" # Generated labels - app.kubernetes.io/version: "v1.11.0" + app.kubernetes.io/version: "v1.13.1" spec: group: cert-manager.io names: @@ -3009,7 +3417,7 @@ spec: description: 'When solving an HTTP-01 challenge, cert-manager creates an HTTPRoute. cert-manager needs to know which parentRefs should be used when creating the HTTPRoute. Usually, the parentRef references a Gateway. See: https://gateway-api.sigs.k8s.io/api-types/httproute/#attaching-to-gateways' type: array items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." type: object required: - name @@ -3021,7 +3429,7 @@ spec: maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ kind: - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." type: string default: Gateway maxLength: 63 @@ -3033,19 +3441,19 @@ spec: maxLength: 253 minLength: 1 namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" type: string maxLength: 63 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " type: integer format: int32 maximum: 65535 minimum: 1 sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" type: string maxLength: 253 minLength: 1 @@ -3058,7 +3466,10 @@ spec: type: object properties: class: - description: The ingress class to use when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of 'class' or 'name' may be specified. + description: This field configures the annotation `kubernetes.io/ingress.class` when creating Ingress resources to solve ACME challenges that use this challenge solver. Only one of `class`, `name` or `ingressClassName` may be specified. + type: string + ingressClassName: + description: This field configures the field `ingressClassName` on the created Ingress resources used to solve ACME challenges that use this challenge solver. This is the recommended way of configuring the ingress class. Only one of `class`, `name` or `ingressClassName` may be specified. type: string ingressTemplate: description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. @@ -3079,7 +3490,7 @@ spec: additionalProperties: type: string name: - description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. + description: The name of the ingress resource that should have ACME challenge solving routes inserted into it in order to solve HTTP01 challenges. This is typically used in conjunction with ingress controllers like ingress-gce, which maintains a 1:1 mapping between external IPs and ingress resources. Only one of `class`, `name` or `ingressClassName` may be specified. type: string podTemplate: description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. @@ -3100,7 +3511,7 @@ spec: additionalProperties: type: string spec: - description: PodSpec defines overrides for the HTTP01 challenge solver pod. Only the 'priorityClassName', 'nodeSelector', 'affinity', 'serviceAccountName' and 'tolerations' fields are supported currently. All other fields will be ignored. + description: PodSpec defines overrides for the HTTP01 challenge solver pod. Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. All other fields will be ignored. type: object properties: affinity: @@ -3575,6 +3986,17 @@ spec: topologyKey: description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. type: string + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + type: array + items: + description: LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + x-kubernetes-map-type: atomic nodeSelector: description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' type: object @@ -3642,372 +4064,145 @@ spec: type: array items: type: string - ocspServers: - description: The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". - type: array - items: - type: string - secretName: - description: SecretName is the name of the secret used to sign Certificates issued by this Issuer. - type: string - selfSigned: - description: SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object. - type: object - properties: - crlDistributionPoints: - description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set certificate will be issued without CDP. Values are strings. - type: array - items: - type: string - vault: - description: Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend. - type: object - required: - - auth - - path - - server - properties: - auth: - description: Auth configures how cert-manager authenticates with the Vault server. - type: object - properties: - appRole: - description: AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource. - type: object - required: - - path - - roleId - - secretRef - properties: - path: - description: 'Path where the App Role authentication backend is mounted in Vault, e.g: "approle"' - type: string - roleId: - description: RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault. - type: string - secretRef: - description: Reference to a key in a Secret that contains the App Role secret used to authenticate with Vault. The `key` field must be specified and denotes which entry within the Secret resource is used as the app role secret. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - kubernetes: - description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server. - type: object - required: - - role - - secretRef - properties: - mountPath: - description: The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to `/v1/auth/foo`, will use the path `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the default value "/v1/auth/kubernetes" will be used. - type: string - role: - description: A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies. - type: string - secretRef: - description: The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of 'ambient credentials' is not supported. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - tokenSecretRef: - description: TokenSecretRef authenticates with Vault by presenting a token. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - caBundle: - description: Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by Vault. Only used if using HTTPS to connect to Vault and ignored for HTTP connections. Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection. - type: string - format: byte - caBundleSecretRef: - description: Reference to a Secret containing a bundle of PEM-encoded CAs to use when verifying the certificate chain presented by Vault when using HTTPS. Mutually exclusive with CABundle. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection. If no key for the Secret is specified, cert-manager will default to 'ca.crt'. - type: object - required: - - name - properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: 'Name of the vault namespace. Namespaces is a set of features within Vault Enterprise that allows Vault environments to support Secure Multi-tenancy. e.g: "ns1" More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces' - type: string - path: - description: 'Path is the mount path of the Vault PKI backend''s `sign` endpoint, e.g: "my_pki_mount/sign/my-role-name".' - type: string - server: - description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + ocspServers: + description: The OCSP server list is an X.509 v3 extension that defines a list of URLs of OCSP responders. The OCSP responders can be queried for the revocation status of an issued certificate. If not set, the certificate will be issued with no OCSP servers set. For example, an OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". + type: array + items: + type: string + secretName: + description: SecretName is the name of the secret used to sign Certificates issued by this Issuer. type: string - venafi: - description: Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone. + selfSigned: + description: SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object. + type: object + properties: + crlDistributionPoints: + description: The CRL distribution points is an X.509 v3 certificate extension which identifies the location of the CRL from which the revocation of this certificate can be checked. If not set certificate will be issued without CDP. Values are strings. + type: array + items: + type: string + vault: + description: Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend. type: object required: - - zone + - auth + - path + - server properties: - cloud: - description: Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified. + auth: + description: Auth configures how cert-manager authenticates with the Vault server. type: object - required: - - apiTokenSecretRef properties: - apiTokenSecretRef: - description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. + appRole: + description: AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource. type: object required: - - name + - path + - roleId + - secretRef properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + path: + description: 'Path where the App Role authentication backend is mounted in Vault, e.g: "approle"' type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + roleId: + description: RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault. type: string - url: - description: URL is the base URL for Venafi Cloud. Defaults to "https://api.venafi.cloud/v1". - type: string - tpp: - description: TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified. - type: object - required: - - credentialsRef - - url - properties: - caBundle: - description: Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by the TPP server. Only used if using HTTPS; ignored for HTTP. If undefined, the certificate bundle in the cert-manager controller container is used to validate the chain. - type: string - format: byte - credentialsRef: - description: CredentialsRef is a reference to a Secret containing the username and password for the TPP server. The secret must contain two keys, 'username' and 'password'. + secretRef: + description: Reference to a key in a Secret that contains the App Role secret used to authenticate with Vault. The `key` field must be specified and denotes which entry within the Secret resource is used as the app role secret. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + kubernetes: + description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server. type: object required: - - name + - role properties: - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + mountPath: + description: The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to `/v1/auth/foo`, will use the path `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the default value "/v1/auth/kubernetes" will be used. type: string - url: - description: 'URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: "https://tpp.example.com/vedsdk".' - type: string - zone: - description: Zone is the Venafi Policy Zone to use for this issuer. All requests made to the Venafi platform will be restricted by the named zone policy. This field is required. - type: string - status: - description: Status of the Issuer. This is set and managed automatically. - type: object - properties: - acme: - description: ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates. - type: object - properties: - lastRegisteredEmail: - description: LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer - type: string - uri: - description: URI is the unique account identifier, which can also be used to retrieve account details from the CA - type: string - conditions: - description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`. - type: array - items: - description: IssuerCondition contains condition information for an Issuer. - type: object - required: - - status - - type - properties: - lastTransitionTime: - description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. - type: string - format: date-time - message: - description: Message is a human readable description of the details of the last transition, complementing reason. - type: string - observedGeneration: - description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Issuer. - type: integer - format: int64 - reason: - description: Reason is a brief machine readable explanation for the condition's last transition. - type: string - status: - description: Status of the condition, one of (`True`, `False`, `Unknown`). - type: string - enum: - - "True" - - "False" - - Unknown - type: - description: Type of the condition, known values are (`Ready`). - type: string - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - served: true - storage: true ---- -# Source: cert-manager/templates/crds.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: certificates.cert-manager.io - labels: - app: 'cert-manager' - app.kubernetes.io/name: 'cert-manager' - app.kubernetes.io/instance: 'cert-manager' - # Generated labels - app.kubernetes.io/version: "v1.11.0" -spec: - group: cert-manager.io - names: - kind: Certificate - listKind: CertificateList - plural: certificates - shortNames: - - cert - - certs - singular: certificate - categories: - - cert-manager - scope: Namespaced - versions: - - name: v1 - subresources: - status: {} - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .spec.secretName - name: Secret - type: string - - jsonPath: .spec.issuerRef.name - name: Issuer - priority: 1 - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. - name: Age - type: date - schema: - openAPIV3Schema: - description: "A Certificate resource should be created to ensure an up to date and signed x509 certificate is stored in the Kubernetes Secret resource named in `spec.secretName`. \n The stored certificate will be renewed before it expires (as configured by `spec.renewBefore`)." - type: object - required: - - spec - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Desired state of the Certificate resource. - type: object - required: - - issuerRef - - secretName - properties: - additionalOutputFormats: - description: AdditionalOutputFormats defines extra output formats of the private key and signed certificate chain to be written to this Certificate's target Secret. This is an Alpha Feature and is only enabled with the `--feature-gates=AdditionalCertificateOutputFormats=true` option on both the controller and webhook components. - type: array - items: - description: CertificateAdditionalOutputFormat defines an additional output format of a Certificate resource. These contain supplementary data formats of the signed certificate chain and paired private key. - type: object - required: - - type - properties: - type: - description: Type is the name of the format type that should be written to the Certificate's target Secret. - type: string - enum: - - DER - - CombinedPEM - commonName: - description: 'CommonName is a common name to be used on the Certificate. The CommonName should have a length of 64 characters or fewer to avoid generating invalid CSRs. This value is ignored by TLS clients when any subject alt name is set. This is x509 behaviour: https://tools.ietf.org/html/rfc6125#section-6.4.4' - type: string - dnsNames: - description: DNSNames is a list of DNS subjectAltNames to be set on the Certificate. - type: array - items: - type: string - duration: - description: The requested 'duration' (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types. If unset this defaults to 90 days. Certificate will be renewed either 2/3 through its duration or `renewBefore` period before its expiry, whichever is later. Minimum accepted duration is 1 hour. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration - type: string - emailAddresses: - description: EmailAddresses is a list of email subjectAltNames to be set on the Certificate. - type: array - items: - type: string - encodeUsagesInRequest: - description: EncodeUsagesInRequest controls whether key usages should be present in the CertificateRequest - type: boolean - ipAddresses: - description: IPAddresses is a list of IP address subjectAltNames to be set on the Certificate. - type: array - items: - type: string - isCA: - description: IsCA will mark this Certificate as valid for certificate signing. This will automatically add the `cert sign` usage to the list of `usages`. - type: boolean - issuerRef: - description: IssuerRef is a reference to the issuer for this certificate. If the `kind` field is not set, or set to `Issuer`, an Issuer resource with the given name in the same namespace as the Certificate will be used. If the `kind` field is set to `ClusterIssuer`, a ClusterIssuer with the provided name will be used. The `name` field in this stanza is required at all times. - type: object - required: - - name - properties: - group: - description: Group of the resource being referred to. + role: + description: A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies. + type: string + secretRef: + description: The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of 'ambient credentials' is not supported. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + serviceAccountRef: + description: A reference to a service account that will be used to request a bound token (also known as "projected token"). Compared to using "secretRef", using this field means that you don't rely on statically bound tokens. To use this field, you must configure an RBAC rule to let cert-manager request a token. + type: object + required: + - name + properties: + name: + description: Name of the ServiceAccount used to request a token. + type: string + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + caBundle: + description: Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by Vault. Only used if using HTTPS to connect to Vault and ignored for HTTP connections. Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection. type: string - kind: - description: Kind of the resource being referred to. + format: byte + caBundleSecretRef: + description: Reference to a Secret containing a bundle of PEM-encoded CAs to use when verifying the certificate chain presented by Vault when using HTTPS. Mutually exclusive with CABundle. If neither CABundle nor CABundleSecretRef are defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection. If no key for the Secret is specified, cert-manager will default to 'ca.crt'. + type: object + required: + - name + properties: + key: + description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. + type: string + name: + description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Name of the vault namespace. Namespaces is a set of features within Vault Enterprise that allows Vault environments to support Secure Multi-tenancy. e.g: "ns1" More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces' type: string - name: - description: Name of the resource being referred to. + path: + description: 'Path is the mount path of the Vault PKI backend''s `sign` endpoint, e.g: "my_pki_mount/sign/my-role-name".' type: string - keystores: - description: Keystores configures additional keystore output formats stored in the `secretName` Secret resource. + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + venafi: + description: Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone. type: object + required: + - zone properties: - jks: - description: JKS configures options for storing a JKS keystore in the `spec.secretName` Secret resource. + cloud: + description: Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified. type: object required: - - create - - passwordSecretRef + - apiTokenSecretRef properties: - create: - description: Create enables JKS keystore creation for the Certificate. If true, a file named `keystore.jks` will be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef`. The keystore file will be updated immediately. A file named `truststore.jks` will also be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef` containing the issuing Certificate Authority - type: boolean - passwordSecretRef: - description: PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the JKS keystore. + apiTokenSecretRef: + description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. type: object required: - name @@ -4018,167 +4213,57 @@ spec: name: description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' type: string - pkcs12: - description: PKCS12 configures options for storing a PKCS12 keystore in the `spec.secretName` Secret resource. + url: + description: URL is the base URL for Venafi Cloud. Defaults to "https://api.venafi.cloud/v1". + type: string + tpp: + description: TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified. type: object required: - - create - - passwordSecretRef + - credentialsRef + - url properties: - create: - description: Create enables PKCS12 keystore creation for the Certificate. If true, a file named `keystore.p12` will be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef`. The keystore file will be updated immediately. A file named `truststore.p12` will also be created in the target Secret resource, encrypted using the password stored in `passwordSecretRef` containing the issuing Certificate Authority - type: boolean - passwordSecretRef: - description: PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the PKCS12 keystore. + caBundle: + description: Base64-encoded bundle of PEM CAs which will be used to validate the certificate chain presented by the TPP server. Only used if using HTTPS; ignored for HTTP. If undefined, the certificate bundle in the cert-manager controller container is used to validate the chain. + type: string + format: byte + credentialsRef: + description: CredentialsRef is a reference to a Secret containing the username and password for the TPP server. The secret must contain two keys, 'username' and 'password'. type: object required: - name properties: - key: - description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required. - type: string name: description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' type: string - literalSubject: - description: LiteralSubject is an LDAP formatted string that represents the [X.509 Subject field](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6). Use this *instead* of the Subject field if you need to ensure the correct ordering of the RDN sequence, such as when issuing certs for LDAP authentication. See https://github.com/cert-manager/cert-manager/issues/3203, https://github.com/cert-manager/cert-manager/issues/4424. This field is alpha level and is only supported by cert-manager installations where LiteralCertificateSubject feature gate is enabled on both cert-manager controller and webhook. - type: string - privateKey: - description: Options to control private keys used for the Certificate. + url: + description: 'URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: "https://tpp.example.com/vedsdk".' + type: string + zone: + description: Zone is the Venafi Policy Zone to use for this issuer. All requests made to the Venafi platform will be restricted by the named zone policy. This field is required. + type: string + status: + description: Status of the Issuer. This is set and managed automatically. + type: object + properties: + acme: + description: ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates. type: object properties: - algorithm: - description: Algorithm is the private key algorithm of the corresponding private key for this certificate. If provided, allowed values are either `RSA`,`Ed25519` or `ECDSA` If `algorithm` is specified and `size` is not provided, key size of 256 will be used for `ECDSA` key algorithm and key size of 2048 will be used for `RSA` key algorithm. key size is ignored when using the `Ed25519` key algorithm. - type: string - enum: - - RSA - - ECDSA - - Ed25519 - encoding: - description: The private key cryptography standards (PKCS) encoding for this certificate's private key to be encoded in. If provided, allowed values are `PKCS1` and `PKCS8` standing for PKCS#1 and PKCS#8, respectively. Defaults to `PKCS1` if not specified. + lastPrivateKeyHash: + description: LastPrivateKeyHash is a hash of the private key associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer type: string - enum: - - PKCS1 - - PKCS8 - rotationPolicy: - description: RotationPolicy controls how private keys should be regenerated when a re-issuance is being processed. If set to Never, a private key will only be generated if one does not already exist in the target `spec.secretName`. If one does exists but it does not have the correct algorithm or size, a warning will be raised to await user intervention. If set to Always, a private key matching the specified requirements will be generated whenever a re-issuance occurs. Default is 'Never' for backward compatibility. + lastRegisteredEmail: + description: LastRegisteredEmail is the email associated with the latest registered ACME account, in order to track changes made to registered account associated with the Issuer type: string - enum: - - Never - - Always - size: - description: Size is the key bit size of the corresponding private key for this certificate. If `algorithm` is set to `RSA`, valid values are `2048`, `4096` or `8192`, and will default to `2048` if not specified. If `algorithm` is set to `ECDSA`, valid values are `256`, `384` or `521`, and will default to `256` if not specified. If `algorithm` is set to `Ed25519`, Size is ignored. No other values are allowed. - type: integer - renewBefore: - description: How long before the currently issued certificate's expiry cert-manager should renew the certificate. The default is 2/3 of the issued certificate's duration. Minimum accepted value is 5 minutes. Value must be in units accepted by Go time.ParseDuration https://golang.org/pkg/time/#ParseDuration - type: string - revisionHistoryLimit: - description: revisionHistoryLimit is the maximum number of CertificateRequest revisions that are maintained in the Certificate's history. Each revision represents a single `CertificateRequest` created by this Certificate, either when it was created, renewed, or Spec was changed. Revisions will be removed by oldest first if the number of revisions exceeds this number. If set, revisionHistoryLimit must be a value of `1` or greater. If unset (`nil`), revisions will not be garbage collected. Default value is `nil`. - type: integer - format: int32 - secretName: - description: SecretName is the name of the secret resource that will be automatically created and managed by this Certificate resource. It will be populated with a private key and certificate, signed by the denoted issuer. - type: string - secretTemplate: - description: SecretTemplate defines annotations and labels to be copied to the Certificate's Secret. Labels and annotations on the Secret will be changed as they appear on the SecretTemplate when added or removed. SecretTemplate annotations are added in conjunction with, and cannot overwrite, the base set of annotations cert-manager sets on the Certificate's Secret. - type: object - properties: - annotations: - description: Annotations is a key value map to be copied to the target Kubernetes Secret. - type: object - additionalProperties: - type: string - labels: - description: Labels is a key value map to be copied to the target Kubernetes Secret. - type: object - additionalProperties: - type: string - subject: - description: Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name). - type: object - properties: - countries: - description: Countries to be used on the Certificate. - type: array - items: - type: string - localities: - description: Cities to be used on the Certificate. - type: array - items: - type: string - organizationalUnits: - description: Organizational Units to be used on the Certificate. - type: array - items: - type: string - organizations: - description: Organizations to be used on the Certificate. - type: array - items: - type: string - postalCodes: - description: Postal codes to be used on the Certificate. - type: array - items: - type: string - provinces: - description: State/Provinces to be used on the Certificate. - type: array - items: - type: string - serialNumber: - description: Serial number to be used on the Certificate. + uri: + description: URI is the unique account identifier, which can also be used to retrieve account details from the CA type: string - streetAddresses: - description: Street addresses to be used on the Certificate. - type: array - items: - type: string - uris: - description: URIs is a list of URI subjectAltNames to be set on the Certificate. - type: array - items: - type: string - usages: - description: Usages is the set of x509 usages that are requested for the certificate. Defaults to `digital signature` and `key encipherment` if not specified. - type: array - items: - description: "KeyUsage specifies valid usage contexts for keys. See: https://tools.ietf.org/html/rfc5280#section-4.2.1.3 https://tools.ietf.org/html/rfc5280#section-4.2.1.12 \n Valid KeyUsage values are as follows: \"signing\", \"digital signature\", \"content commitment\", \"key encipherment\", \"key agreement\", \"data encipherment\", \"cert sign\", \"crl sign\", \"encipher only\", \"decipher only\", \"any\", \"server auth\", \"client auth\", \"code signing\", \"email protection\", \"s/mime\", \"ipsec end system\", \"ipsec tunnel\", \"ipsec user\", \"timestamping\", \"ocsp signing\", \"microsoft sgc\", \"netscape sgc\"" - type: string - enum: - - signing - - digital signature - - content commitment - - key encipherment - - key agreement - - data encipherment - - cert sign - - crl sign - - encipher only - - decipher only - - any - - server auth - - client auth - - code signing - - email protection - - s/mime - - ipsec end system - - ipsec tunnel - - ipsec user - - timestamping - - ocsp signing - - microsoft sgc - - netscape sgc - status: - description: Status of the Certificate. This is set and managed automatically. - type: object - properties: conditions: - description: List of status conditions to indicate the status of certificates. Known condition types are `Ready` and `Issuing`. + description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`. type: array items: - description: CertificateCondition contains condition information for an Certificate. + description: IssuerCondition contains condition information for an Issuer. type: object required: - status @@ -4192,7 +4277,7 @@ spec: description: Message is a human readable description of the details of the last transition, complementing reason. type: string observedGeneration: - description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Certificate. + description: If set, this represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.condition[x].observedGeneration is 9, the condition is out of date with respect to the current state of the Issuer. type: integer format: int64 reason: @@ -4206,36 +4291,11 @@ spec: - "False" - Unknown type: - description: Type of the condition, known values are (`Ready`, `Issuing`). + description: Type of the condition, known values are (`Ready`). type: string x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map - failedIssuanceAttempts: - description: The number of continuous failed issuance attempts up till now. This field gets removed (if set) on a successful issuance and gets set to 1 if unset and an issuance has failed. If an issuance has failed, the delay till the next issuance will be calculated using formula time.Hour * 2 ^ (failedIssuanceAttempts - 1). - type: integer - lastFailureTime: - description: LastFailureTime is the time as recorded by the Certificate controller of the most recent failure to complete a CertificateRequest for this Certificate resource. If set, cert-manager will not re-request another Certificate until 1 hour has elapsed from this time. - type: string - format: date-time - nextPrivateKeySecretName: - description: The name of the Secret resource containing the private key to be used for the next certificate iteration. The keymanager controller will automatically set this field if the `Issuing` condition is set to `True`. It will automatically unset this field when the Issuing condition is not set or False. - type: string - notAfter: - description: The expiration time of the certificate stored in the secret named by this resource in `spec.secretName`. - type: string - format: date-time - notBefore: - description: The time after which the certificate stored in the secret named by this resource in spec.secretName is valid. - type: string - format: date-time - renewalTime: - description: RenewalTime is the time at which the certificate will be next renewed. If not set, no upcoming renewal is scheduled. - type: string - format: date-time - revision: - description: "The current 'revision' of the certificate as issued. \n When a CertificateRequest resource is created, it will have the `cert-manager.io/certificate-revision` set to one greater than the current value of this field. \n Upon issuance, this field will be set to the value of the annotation on the CertificateRequest resource used to issue the certificate. \n Persisting the value on the CertificateRequest resource allows the certificates controller to know whether a request is part of an old issuance or if it is part of the ongoing revision's issuance by checking if the revision value in the annotation is greater than this field." - type: integer served: true storage: true --- @@ -4249,7 +4309,7 @@ metadata: app.kubernetes.io/name: 'cert-manager' app.kubernetes.io/instance: 'cert-manager' # Generated labels - app.kubernetes.io/version: "v1.11.0" + app.kubernetes.io/version: "v1.13.1" spec: group: acme.cert-manager.io names: diff --git a/packages/helm/symphony/files/metric-middleware.json b/packages/helm/symphony/files/metric-middleware.json new file mode 100644 index 000000000..b814f7595 --- /dev/null +++ b/packages/helm/symphony/files/metric-middleware.json @@ -0,0 +1,29 @@ +{ + "type": "middleware.http.metrics", + "properties": { + "serviceName": "symphony-api", + "pipelines": [ + {{- if .Values.otelCollectorAddress }} + { + "exporter": { + "type": "metrics.exporters.otlpgrpc", + "collectorUrl": "{{ tpl .Values.otelCollectorAddress $ }}", + "temporality": false + } + } + {{- end }} + {{- if .Values.genevaCollectorAddress }} + {{- if .Values.otelCollectorAddress }} + , + {{- end }} + { + "exporter": { + "type": "metrics.exporters.otlpgrpc", + "collectorUrl": "{{ tpl .Values.genevaCollectorAddress $ }}", + "temporality": true + } + } + {{- end }} + ] + } + } \ No newline at end of file diff --git a/packages/helm/symphony/files/oss/delete-objects.sh b/packages/helm/symphony/files/oss/delete-objects.sh index 775a9662f..524286c8d 100755 --- a/packages/helm/symphony/files/oss/delete-objects.sh +++ b/packages/helm/symphony/files/oss/delete-objects.sh @@ -5,6 +5,7 @@ SOLUTION_GROUP=solution.symphony FABRIC_GROUP=fabric.symphony AI_GROUP=ai.symphony WORKFLOW_GROUP=workflow.symphony +FEDERATION_GROUP=federation.symphony function delete_crds { local resource_type=$1 @@ -24,3 +25,5 @@ delete_crds "devices.$FABRIC_GROUP" delete_crds "models.$AI_GROUP" delete_crds "skills.$AI_GROUP" delete_crds "skillpackages.$AI_GROUP" +delete_crds "catalogs.$FEDERATION_GROUP" +delete_crds "sites.$FEDERATION_GROUP" diff --git a/packages/helm/symphony/files/oss/remove-finalizers.sh b/packages/helm/symphony/files/oss/remove-finalizers.sh index 85a5ce923..c9d0d24b1 100755 --- a/packages/helm/symphony/files/oss/remove-finalizers.sh +++ b/packages/helm/symphony/files/oss/remove-finalizers.sh @@ -8,6 +8,7 @@ SOLUTION_GROUP=solution.symphony FABRIC_GROUP=fabric.symphony AI_GROUP=ai.symphony WORKFLOW_GROUP=workflow.symphony +FEDERATION_GROUP=federation.symphony patchResource() { local resource_type="$1" @@ -59,4 +60,6 @@ remove_finalizers "targets.$FABRIC_GROUP" remove_finalizers "devices.$FABRIC_GROUP" remove_finalizers "models.$AI_GROUP" remove_finalizers "skills.$AI_GROUP" -remove_finalizers "skillpackages.$AI_GROUP" \ No newline at end of file +remove_finalizers "skillpackages.$AI_GROUP" +remove_finalizers "catalogs.$FEDERATION_GROUP" +remove_finalizers "sites.$FEDERATION_GROUP" \ No newline at end of file diff --git a/packages/helm/symphony/files/symphony-api.json b/packages/helm/symphony/files/symphony-api.json index befba9948..a3647614f 100644 --- a/packages/helm/symphony/files/symphony-api.json +++ b/packages/helm/symphony/files/symphony-api.json @@ -212,7 +212,7 @@ "type": "managers.symphony.jobs", "properties": { "providers.state": "mem-state", - "baseUrl": "http://symphony-service:8080/v1alpha2/", + "baseUrl": {{ include "symphony.url" . | quote}}, "user": "admin", "password": "", "interval": "#15", @@ -584,7 +584,7 @@ { "type": "bindings.http", "config": { - "port": 8080, + "port": {{ include "symphony.apiContainerPortHttp" . }}, "pipeline": [ {{- include "symphony.zipkinMiddleware" . | indent 10 }} { @@ -673,12 +673,52 @@ { "type": "bindings.http", "config": { - "port": 8081, + "port": {{ include "symphony.apiContainerPortHttps" . }}, "tls": true, "certProvider": { - "type": "certs.autogen", - "config":{} - } + "type": "certs.localfile", + "config": { + "name": "symphony-serving-cert", + "cert": "{{- include "symphony.apiServingCert" . -}}", + "key": "{{- include "symphony.apiServingKey" . -}}" + } + }, + "pipeline": [ + {{- include "symphony.zipkinMiddleware" . | indent 10 }} + { + "type": "middleware.http.cors", + "properties": { + "Access-Control-Allow-Headers": "authorization,Content-Type", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Methods": "HEAD,GET,POST,PUT,DELETE,OPTIONS", + "Access-Control-Allow-Origin": "*" + } + }, + { + "type": "middleware.http.jwt", + "properties": { + "ignorePaths": [ + "/v1alpha2/users/auth", + "/v1alpha2/solution/instances", + "/v1alpha2/agent/references", + "/v1alpha2/greetings", + "/v1alpha2/agent/config" + ], + "verifyKey": "SymphonyKey", + "authServer": "kubernetes", + "enableRBAC": false + } + }, + { + "type": "middleware.http.telemetry", + "properties": { + "enabled": true, + "maxBatchSize": 8192, + "maxBatchIntervalSeconds": 2, + "client": "my-dev-machine" + } + } + ] } } ] diff --git a/packages/helm/symphony/files/trace-middleware.json b/packages/helm/symphony/files/trace-middleware.json new file mode 100644 index 000000000..a72d9ccd6 --- /dev/null +++ b/packages/helm/symphony/files/trace-middleware.json @@ -0,0 +1,16 @@ +{ + "type": "middleware.http.tracing", + "properties": { + "serviceName": "symphony-api", + "pipelines": [ + {{- if .Values.otelCollectorAddress }} + { + "exporter": { + "type": "tracing.exporters.otlpgrpc", + "collectorUrl": "{{ tpl .Values.otelCollectorAddress $ }}" + } + } + {{- end }} + ] + } + } \ No newline at end of file diff --git a/packages/helm/symphony/templates/certificate/certificate.yaml b/packages/helm/symphony/templates/certificate/certificate.yaml new file mode 100644 index 000000000..32a245d92 --- /dev/null +++ b/packages/helm/symphony/templates/certificate/certificate.yaml @@ -0,0 +1,15 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "symphony.apiServingCertName" . }} + namespace: {{ .Release.Namespace }} +spec: + issuerRef: + name: {{ include "symphony.apiServingCertIssuerName" . }} + kind: Issuer + secretName: {{ include "symphony.apiServingCertName" . }} + dnsNames: + - {{ include "symphony.serviceName" . }} + - {{ printf "%s.%s" (include "symphony.serviceName" .) .Release.Namespace }} + - {{ printf "%s.%s.svc" (include "symphony.serviceName" .) .Release.Namespace }} + - {{ printf "%s.%s.svc.cluster.local" (include "symphony.serviceName" .) .Release.Namespace }} \ No newline at end of file diff --git a/packages/helm/symphony/templates/env-configmap.yaml b/packages/helm/symphony/templates/env-configmap.yaml new file mode 100644 index 000000000..8efd5df6c --- /dev/null +++ b/packages/helm/symphony/templates/env-configmap.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "symphony.envConfigName" . }} + namespace: {{ .Release.Namespace }} +data: + APP_VERSION: {{ .Chart.AppVersion }} + CHART_VERSION: {{ .Chart.Version }} + API_SERVING_CA: {{ include "symphony.apiServingCA" . }} + SYMPHONY_API_URL: {{ include "symphony.url" . }} + USE_SERVICE_ACCOUNT_TOKENS: "true" \ No newline at end of file diff --git a/packages/helm/symphony/templates/extension-identity.yaml b/packages/helm/symphony/templates/identity/extension-identity.yaml similarity index 89% rename from packages/helm/symphony/templates/extension-identity.yaml rename to packages/helm/symphony/templates/identity/extension-identity.yaml index a82f4cddb..9b0be6a21 100644 --- a/packages/helm/symphony/templates/extension-identity.yaml +++ b/packages/helm/symphony/templates/identity/extension-identity.yaml @@ -1,4 +1,4 @@ -{{ if .Values.global.azure.identity.enabled -}} +{{ if .Values.global.azure.identity.isEnabled -}} apiVersion: clusterconfig.azure.com/v1beta1 kind: AzureExtensionIdentity metadata: diff --git a/packages/helm/symphony/templates/hooks-cluster-role-binding.yaml b/packages/helm/symphony/templates/identity/hooks-cluster-role-binding.yaml similarity index 100% rename from packages/helm/symphony/templates/hooks-cluster-role-binding.yaml rename to packages/helm/symphony/templates/identity/hooks-cluster-role-binding.yaml diff --git a/packages/helm/symphony/templates/hooks-cluster-role.yaml b/packages/helm/symphony/templates/identity/hooks-cluster-role.yaml similarity index 100% rename from packages/helm/symphony/templates/hooks-cluster-role.yaml rename to packages/helm/symphony/templates/identity/hooks-cluster-role.yaml diff --git a/packages/helm/symphony/templates/hooks-service-account.yaml b/packages/helm/symphony/templates/identity/hooks-service-account.yaml similarity index 100% rename from packages/helm/symphony/templates/hooks-service-account.yaml rename to packages/helm/symphony/templates/identity/hooks-service-account.yaml diff --git a/packages/helm/symphony/templates/pai-cluster-role-binding.yaml b/packages/helm/symphony/templates/identity/pai-cluster-role-binding.yaml similarity index 100% rename from packages/helm/symphony/templates/pai-cluster-role-binding.yaml rename to packages/helm/symphony/templates/identity/pai-cluster-role-binding.yaml diff --git a/packages/helm/symphony/templates/pai-cluster-role.yaml b/packages/helm/symphony/templates/identity/pai-cluster-role.yaml similarity index 100% rename from packages/helm/symphony/templates/pai-cluster-role.yaml rename to packages/helm/symphony/templates/identity/pai-cluster-role.yaml diff --git a/packages/helm/symphony/templates/pai-service-account.yaml b/packages/helm/symphony/templates/identity/pai-service-account.yaml similarity index 100% rename from packages/helm/symphony/templates/pai-service-account.yaml rename to packages/helm/symphony/templates/identity/pai-service-account.yaml diff --git a/packages/helm/symphony/templates/outbound-proxy-secret.yaml b/packages/helm/symphony/templates/outbound-proxy-secret.yaml new file mode 100644 index 000000000..223e757d6 --- /dev/null +++ b/packages/helm/symphony/templates/outbound-proxy-secret.yaml @@ -0,0 +1,29 @@ +{{ if .Values.Azure.proxySettings.isProxyEnabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "symphony.fullname" . }}-proxy-config + namespace: {{ .Release.Namespace }} +type: Opaque +data: +{{ if .Values.Azure.proxySettings.httpProxy }} + HTTP_PROXY: {{ .Values.Azure.proxySettings.httpProxy | b64enc | quote }} +{{end}} +{{ if .Values.Azure.proxySettings.httpsProxy }} + HTTPS_PROXY: {{ .Values.Azure.proxySettings.httpsProxy | b64enc | quote }} +{{end}} +{{ if .Values.Azure.proxySettings.noProxy }} + NO_PROXY: {{ .Values.Azure.proxySettings.noProxy| b64enc | quote }} +{{ end }} +{{ end }} +--- +{{ if and (or .Values.Azure.proxySettings.isCustomCert .Values.Azure.proxySettings.isProxyEnabled) (.Values.Azure.proxySettings.proxyCert) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "symphony.fullname" . }}-proxy-cert + namespace: {{ .Release.Namespace }} +type: Opaque +data: + proxy-cert.crt: {{ .Values.Azure.proxySettings.proxyCert | b64enc | quote }} +{{ end }} \ No newline at end of file diff --git a/packages/helm/symphony/templates/symphony-api.yaml b/packages/helm/symphony/templates/symphony-api.yaml deleted file mode 100644 index b288691fe..000000000 --- a/packages/helm/symphony/templates/symphony-api.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "symphony.fullname" . }}-api - labels: - app: {{ include "symphony.appSelector" . }} - namespace: {{ .Release.Namespace }} -spec: - replicas: 1 - selector: - matchLabels: - app: {{ include "symphony.appSelector" . }} - template: - metadata: - labels: - app: {{ include "symphony.appSelector" . }} - spec: - {{- with .Values.affinity }} - affinity: -{{ toYaml . | indent 8 }} - {{- end }} - serviceAccountName: {{ include "symphony.serviceAccountName" . }} - containers: - - name: symphony-api - securityContext: {{- toYaml .Values.securityContext | nindent 12 }} - image: {{ .Values.paiImage.repository }}:{{ .Values.paiImage.tag }} - imagePullPolicy: {{ .Values.paiImage.pullPolicy }} - ports: - - containerPort: 8080 - - containerPort: 8081 - env: - - name: "HELM_NAMESPACE" - value: default - - name: "CONFIG" - value: /etc/symphony-api/config/symphony-api.json - envFrom: - - secretRef: - name: {{ include "symphony.fullname" . }}-auth - volumeMounts: - - name: symphony-api-config - mountPath: /etc/symphony-api/config - {{- if .Values.global.azure.identity.mSIAdapterYaml }} - - name: msi-adapter - env: - - name: TOKEN_NAMESPACE - value: {{ .Release.Namespace }} - {{- .Values.global.azure.identity.mSIAdapterYaml | nindent 8 }} - {{- end }} - volumes: - - name: symphony-api-config - configMap: - name: {{ include "symphony.configmapName" . }} \ No newline at end of file diff --git a/packages/helm/symphony/templates/_helpers.tpl b/packages/helm/symphony/templates/symphony-core/_helpers.tpl similarity index 53% rename from packages/helm/symphony/templates/_helpers.tpl rename to packages/helm/symphony/templates/symphony-core/_helpers.tpl index 5f2cd8de8..45343e959 100644 --- a/packages/helm/symphony/templates/_helpers.tpl +++ b/packages/helm/symphony/templates/symphony-core/_helpers.tpl @@ -30,6 +30,13 @@ Create chart name and version as used by the chart label. {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} +{{/* +Symphony Service Name +*/}} +{{- define "symphony.serviceName" -}} +{{- printf "%s-service" (include "symphony.fullname" .) }} +{{- end }} + {{/* Common labels */}} @@ -64,6 +71,20 @@ Configmap Name {{- printf "%s-api-config" (include "symphony.fullname" .) }} {{- end }} +{{/* +Symphony Api Container Http Port +*/}} +{{- define "symphony.apiContainerPortHttp" -}} +{{- default 8080 .Values.api.apiContainerPortHttp }} +{{- end }} + +{{/* +Symphony Api Container Https Port +*/}} +{{- define "symphony.apiContainerPortHttps" -}} +{{- default 8081 .Values.api.apiContainerPortHttps }} +{{- end }} + {{/* App Selector */}} @@ -79,3 +100,58 @@ Zipkin Middleware {{ tpl (.Files.Get "files/zipkin-middleware.json") . }}, {{- end }} {{- end }} + +{{/* +Symphony API serving certs directory path +*/}} +{{- define "symphony.apiServingCertsDir" -}} +{{- printf "/etc/%s-api/tls" (include "symphony.fullname" .) }} +{{- end }} + +{{/* +Symphony API serving certificate path +*/}} +{{- define "symphony.apiServingCert" -}} +{{- printf "%s/%s" (include "symphony.apiServingCertsDir" .) "tls.crt" }} +{{- end }} + +{{/* +Symphony API serving certificate key path +*/}} +{{- define "symphony.apiServingKey" -}} +{{- printf "%s/%s" (include "symphony.apiServingCertsDir" .) "tls.key" }} +{{- end }} + +{{/* +Symphony API serving certificate Name +*/}} +{{- define "symphony.apiServingCertName" -}} +{{ printf "%s%s" (include "symphony.fullname" .) "-api-serving-cert"}} +{{- end }} + + +{{/* +Symphony API serving certificate CA path +*/}} +{{- define "symphony.apiServingCA" -}} +{{- printf "%s/%s" (include "symphony.apiServingCertsDir" .) "ca.crt" }} +{{- end }} + +{{/* +Symphony API ServingCertIssuerName +*/}} +{{- define "symphony.apiServingCertIssuerName" -}} +{{- printf "%s%s" (include "symphony.fullname" .) "-selfsigned-issuer"}} +{{- end }} + +{{/* +Symphony full url Endpoint +*/}} +{{- define "symphony.url" -}} +{{- printf "https://%s:%s/v1alpha2/" (include "symphony.serviceName" .) (include "symphony.apiContainerPortHttps" .) }} +{{- end }} + +{{/* Symphony Env Config Name */}} +{{- define "symphony.envConfigName" -}} +{{- printf "%s-env-config" (include "symphony.fullname" .) }} +{{- end }} \ No newline at end of file diff --git a/packages/helm/symphony/templates/delete-job.yaml b/packages/helm/symphony/templates/symphony-core/delete-job.yaml similarity index 100% rename from packages/helm/symphony/templates/delete-job.yaml rename to packages/helm/symphony/templates/symphony-core/delete-job.yaml diff --git a/packages/helm/symphony/templates/remove-finializers-job.yaml b/packages/helm/symphony/templates/symphony-core/remove-finializers-job.yaml similarity index 100% rename from packages/helm/symphony/templates/remove-finializers-job.yaml rename to packages/helm/symphony/templates/symphony-core/remove-finializers-job.yaml diff --git a/packages/helm/symphony/templates/symphony-core/symphony-api.yaml b/packages/helm/symphony/templates/symphony-core/symphony-api.yaml new file mode 100644 index 000000000..ddcb07f19 --- /dev/null +++ b/packages/helm/symphony/templates/symphony-core/symphony-api.yaml @@ -0,0 +1,110 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "symphony.fullname" . }}-api + labels: + app: {{ include "symphony.appSelector" . }} + namespace: {{ .Release.Namespace }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ include "symphony.appSelector" . }} + template: + metadata: + labels: + app: {{ include "symphony.appSelector" . }} + spec: + {{- with .Values.affinity }} + affinity: + {{ toYaml . | indent 8 }} + {{- end }} + serviceAccountName: {{ include "symphony.serviceAccountName" . }} + {{- if and .Values.Azure.proxySettings.isProxyEnabled .Values.Azure.proxySettings.proxyCert }} + initContainers: + - name: init-porxy-cert-dir + image: mcr.microsoft.com/mirror/docker/library/busybox:1.35 + command: ['sh', '-c', "mkdir -p /etc/pki/ca-trust/extracted/pem /etc/pki/ca-trust/extracted/openssl /etc/pki/ca-trust/extracted/java /etc/pki/ca-trust/extracted/edk2"] + volumeMounts: + - name: ssl-certs + mountPath: /etc/pki/ca-trust/extracted/ + {{ end }} + containers: + - name: symphony-api + securityContext: {{- toYaml .Values.securityContext | nindent 12 }} + image: {{ .Values.paiImage.repository }}:{{ .Values.paiImage.tag }} + imagePullPolicy: {{ .Values.paiImage.pullPolicy }} + ports: + - containerPort: {{ include "symphony.apiContainerPortHttp" . }} + - containerPort: {{ include "symphony.apiContainerPortHttps" . }} + env: + - name: "HELM_NAMESPACE" + value: default + - name: "CONFIG" + value: /etc/symphony-api/config/symphony-api.json + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: SERVICE_ACCOUNT_NAME + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName + - name: SYMPHONY_CONTROLLER_SERVICE_ACCOUNT_NAME + value: '{{ include "symphony.fullname" . }}-controller-manager' + + envFrom: + - secretRef: + name: {{ include "symphony.fullname" . }}-auth + {{ if .Values.Azure.proxySettings.isProxyEnabled }} + - secretRef: + name: {{ include "symphony.fullname" . }}-proxy-config + {{ end }} + - configMapRef: + name: {{ include "symphony.envConfigName" . }} + + volumeMounts: + - name: symphony-api-config + mountPath: /etc/symphony-api/config + - mountPath: /var/run/secrets/tokens + name: symphony-api-token + - mountPath: {{ include "symphony.apiServingCertsDir" . }} + name: serving-cert + readOnly: true + {{- if and .Values.Azure.proxySettings.isProxyEnabled .Values.Azure.proxySettings.proxyCert }} + - name: ssl-certs + mountPath: /etc/pki/ca-trust/extracted/ + readOnly: false + - mountPath: /etc/pki/ca-trust/source/anchors/proxy-cert.crt + subPath: proxy-cert.crt + name: proxy-certstore + {{- end }} + + {{- if .Values.global.azure.identity.mSIAdapterYaml }} + - name: msi-adapter + env: + - name: TOKEN_NAMESPACE + value: {{ .Release.Namespace }} + {{- .Values.global.azure.identity.mSIAdapterYaml | nindent 8 }} + {{- end }} + volumes: + - name: symphony-api-config + configMap: + name: {{ include "symphony.configmapName" . }} + - name: symphony-api-token + projected: + sources: + - serviceAccountToken: + path: symphony-api-token + expirationSeconds: 600 + audience: {{ include "symphony.url" . }} + - name: serving-cert + secret: + secretName: {{ include "symphony.apiServingCertName" . }} + {{- if and .Values.Azure.proxySettings.isProxyEnabled .Values.Azure.proxySettings.proxyCert }} + - name: proxy-certstore + secret: + secretName: {{ include "symphony.fullname" . }}-proxy-cert + - name: ssl-certs + emptyDir: {} + {{ end }} diff --git a/packages/helm/symphony/templates/symphony-service-ext.yaml b/packages/helm/symphony/templates/symphony-core/symphony-service-ext.yaml similarity index 61% rename from packages/helm/symphony/templates/symphony-service-ext.yaml rename to packages/helm/symphony/templates/symphony-core/symphony-service-ext.yaml index 48d8292f6..c8108f294 100644 --- a/packages/helm/symphony/templates/symphony-service-ext.yaml +++ b/packages/helm/symphony/templates/symphony-core/symphony-service-ext.yaml @@ -11,11 +11,11 @@ spec: type: LoadBalancer ports: - protocol: TCP - port: 8080 - targetPort: 8080 + port: {{ .Values.symphony.extension.httpport }} + targetPort: {{ include "symphony.apiContainerPortHttp" . }} name: http - protocol: TCP - port: 8081 - targetPort: 8081 + port: {{ .Values.symphony.extension.httpsport }} + targetPort: {{ include "symphony.apiContainerPortHttps" . }} name: https {{- end }} \ No newline at end of file diff --git a/packages/helm/symphony/templates/symphony-core/symphony-service.yaml b/packages/helm/symphony/templates/symphony-core/symphony-service.yaml new file mode 100644 index 000000000..9df86739e --- /dev/null +++ b/packages/helm/symphony/templates/symphony-core/symphony-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "symphony.serviceName" . }} + namespace: {{ .Release.Namespace }} +spec: + selector: + app: symphony-api + type: ClusterIP + ports: + - protocol: TCP + port: {{ .Values.symphony.incluster.httpport }} + targetPort: {{ include "symphony.apiContainerPortHttp" . }} + name: http + - protocol: TCP + port: {{ .Values.symphony.incluster.httpsport }} + targetPort: {{ include "symphony.apiContainerPortHttps" . }} + name: https \ No newline at end of file diff --git a/packages/helm/symphony/templates/symphony.yaml b/packages/helm/symphony/templates/symphony-core/symphonyk8s.yaml similarity index 60% rename from packages/helm/symphony/templates/symphony.yaml rename to packages/helm/symphony/templates/symphony-core/symphonyk8s.yaml index 473f3fac4..a0b623fd9 100644 --- a/packages/helm/symphony/templates/symphony.yaml +++ b/packages/helm/symphony/templates/symphony-core/symphonyk8s.yaml @@ -91,6 +91,117 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: campaigncontainers.workflow.symphony +spec: + group: workflow.symphony + names: + kind: CampaignContainer + listKind: CampaignContainerList + plural: campaigncontainers + singular: campaigncontainer + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: CampaignContainer is the Schema for the campaigns API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "symphony.fullname" @@ -180,6 +291,119 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: catalogcontainers.federation.symphony +spec: + group: federation.symphony + names: + kind: CatalogContainer + listKind: CatalogContainerList + plural: catalogcontainers + singular: catalogcontainer + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: CatalogContainer is the Schema for the catalogs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "symphony.fullname" @@ -265,24 +489,12 @@ spec: properties: type: object x-kubernetes-preserve-unknown-fields: true - siteId: - type: string type: type: string required: - properties - - siteId - type type: object - status: - properties: - properties: - additionalProperties: - type: string - type: object - required: - - properties - type: object type: object served: true storage: true @@ -378,43 +590,22 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "symphony.fullname" - . }}-serving-cert' controller-gen.kubebuilder.io/version: v0.11.1 - name: instances.solution.symphony + creationTimestamp: null + name: instancecontainers.solution.symphony spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - name: '{{ include "symphony.fullname" . }}-webhook-service' - namespace: '{{ .Release.Namespace }}' - path: /convert - conversionReviewVersions: - - v1 group: solution.symphony names: - kind: Instance - listKind: InstanceList - plural: instances - singular: instance + kind: InstanceContainer + listKind: InstanceContainerList + plural: instancecontainers + singular: instancecontainer scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .status.properties.status - name: Status - type: string - - jsonPath: .status.properties.targets - name: Targets - type: string - - jsonPath: .status.properties.deployed - name: Deployed - type: string - name: v1 + - name: v1 schema: openAPIV3Schema: - description: Instance is the Schema for the instances API + description: InstanceContainer is the Schema for the InstanceContainers API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -430,9 +621,143 @@ spec: type: object spec: properties: - arguments: - additionalProperties: - additionalProperties: + displayName: + type: string + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "symphony.fullname" + . }}-serving-cert' + controller-gen.kubebuilder.io/version: v0.11.1 + name: instances.solution.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: '{{ include "symphony.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /convert + conversionReviewVersions: + - v1 + group: solution.symphony + names: + kind: Instance + listKind: InstanceList + plural: instances + singular: instance + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.properties.status + name: Status + type: string + - jsonPath: .status.properties.targets + name: Targets + type: string + - jsonPath: .status.properties.deployed + name: Deployed + type: string + name: v1 + schema: + openAPIV3Schema: + description: Instance is the Schema for the instances API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + arguments: + additionalProperties: + additionalProperties: type: string type: object type: object @@ -1081,6 +1406,119 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: solutioncontainers.solution.symphony +spec: + group: solution.symphony + names: + kind: SolutionContainer + listKind: SolutionContainerList + plural: solutioncontainers + singular: solutioncontainer + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: SolutionContainer is the Schema for the SolutionContainer API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "symphony.fullname" @@ -1205,6 +1643,8 @@ spec: additionalProperties: type: string type: object + rootResource: + type: string version: description: Defines the version of a particular resource type: string @@ -1229,27 +1669,861 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "symphony.fullname" - . }}-serving-cert' controller-gen.kubebuilder.io/version: v0.11.1 - name: targets.fabric.symphony + creationTimestamp: null + name: targetcontainers.fabric.symphony spec: - conversion: - strategy: Webhook - webhook: - clientConfig: - service: - name: '{{ include "symphony.fullname" . }}-webhook-service' - namespace: '{{ .Release.Namespace }}' + group: fabric.symphony + names: + kind: TargetContainer + listKind: TargetContainerList + plural: targetcontainers + singular: targetcontainer + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.properties.status + name: Status + type: string + name: v1 + schema: + openAPIV3Schema: + description: Target is the Schema for the targets API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + type: object + status: + description: TargetContainerStatus defines the observed state of Target + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + description: 'Important: Run "make" to regenerate code after modifying + this file' + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ include "symphony.fullname" + . }}-serving-cert' + controller-gen.kubebuilder.io/version: v0.11.1 + name: targets.fabric.symphony +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: '{{ include "symphony.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' path: /convert conversionReviewVersions: - v1 group: fabric.symphony names: - kind: Target - listKind: TargetList - plural: targets - singular: target + kind: Target + listKind: TargetList + plural: targets + singular: target + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.properties.status + name: Status + type: string + name: v1 + schema: + openAPIV3Schema: + description: Target is the Schema for the targets API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Defines the desired state of Target + properties: + components: + items: + description: Defines a desired runtime component + properties: + constraints: + type: string + dependencies: + items: + type: string + type: array + metadata: + additionalProperties: + type: string + type: object + name: + type: string + properties: + type: object + x-kubernetes-preserve-unknown-fields: true + routes: + items: + properties: + filters: + items: + properties: + direction: + type: string + parameters: + additionalProperties: + type: string + type: object + type: + type: string + required: + - direction + - type + type: object + type: array + properties: + additionalProperties: + type: string + type: object + route: + type: string + type: + type: string + required: + - route + - type + type: object + type: array + sidecars: + items: + properties: + name: + type: string + properties: + type: object + x-kubernetes-preserve-unknown-fields: true + type: + type: string + type: object + type: array + skills: + items: + type: string + type: array + type: + type: string + required: + - name + - type + type: object + type: array + constraints: + type: string + displayName: + type: string + forceRedeploy: + type: boolean + generation: + type: string + metadata: + additionalProperties: + type: string + type: object + properties: + additionalProperties: + type: string + type: object + reconciliationPolicy: + description: Optional ReconcilicationPolicy to specify how target + controller should reconcile. Now only periodic reconciliation is + supported. If the interval is 0, it will only reconcile when the + instance is created or updated. + properties: + interval: + type: string + state: + type: string + required: + - state + type: object + scope: + type: string + topologies: + items: + description: TopologySpec defines the desired device topology the + instance + properties: + bindings: + items: + properties: + config: + additionalProperties: + type: string + type: object + provider: + type: string + role: + type: string + required: + - provider + - role + type: object + type: array + device: + type: string + selector: + additionalProperties: + type: string + type: object + type: object + type: array + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedcampaigns.workflow.symphony +spec: + group: workflow.symphony + names: + kind: VersionedCampaign + listKind: VersionedCampaignList + plural: versionedcampaigns + singular: versionedcampaign + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedCampaign is the Schema for the campaigns API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedcatalogs.federation.symphony +spec: + group: federation.symphony + names: + kind: VersionedCatalog + listKind: VersionedCatalogList + plural: versionedcatalogs + singular: versionedcatalog + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedCatalog is the Schema for the catalogs API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedinstances.solution.symphony +spec: + group: solution.symphony + names: + kind: VersionedInstance + listKind: VersionedInstanceList + plural: versionedinstances + singular: versionedinstance + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedInstance1 is the Schema for the versionedinstances API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedsolutions.solution.symphony +spec: + group: solution.symphony + names: + kind: VersionedSolution + listKind: VersionedSolutionList + plural: versionedsolutions + singular: versionedsolution + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VersionedSolution is the Schema for the VersionedSolution API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + displayName: + type: string + metadata: + additionalProperties: + type: string + type: object + type: object + status: + properties: + lastModified: + format: date-time + type: string + properties: + additionalProperties: + type: string + type: object + provisioningStatus: + description: Defines the state of the ARM resource for long running + operations + properties: + error: + description: Defines an error in the ARM resource for long running + operations + properties: + code: + type: string + details: + items: + description: Defines an error for symphony target + properties: + code: + type: string + details: + items: + description: Defines an error for components defined + in symphony + properties: + code: + type: string + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + type: array + message: + type: string + target: + type: string + type: object + failureCause: + type: string + logErrors: + type: boolean + operationId: + type: string + output: + additionalProperties: + type: string + type: object + status: + type: string + required: + - operationId + - status + type: object + required: + - properties + - provisioningStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: versionedtargets.fabric.symphony +spec: + group: fabric.symphony + names: + kind: VersionedTarget + listKind: VersionedTargetList + plural: versionedtargets + singular: versionedtarget scope: Namespaced versions: - additionalPrinterColumns: @@ -1274,144 +2548,17 @@ spec: metadata: type: object spec: - description: Defines the desired state of Target + description: Defines the desired state of VersionedTargetSpec properties: - components: - items: - description: Defines a desired runtime component - properties: - constraints: - type: string - dependencies: - items: - type: string - type: array - metadata: - additionalProperties: - type: string - type: object - name: - type: string - properties: - type: object - x-kubernetes-preserve-unknown-fields: true - routes: - items: - properties: - filters: - items: - properties: - direction: - type: string - parameters: - additionalProperties: - type: string - type: object - type: - type: string - required: - - direction - - type - type: object - type: array - properties: - additionalProperties: - type: string - type: object - route: - type: string - type: - type: string - required: - - route - - type - type: object - type: array - sidecars: - items: - properties: - name: - type: string - properties: - type: object - x-kubernetes-preserve-unknown-fields: true - type: - type: string - type: object - type: array - skills: - items: - type: string - type: array - type: - type: string - required: - - name - - type - type: object - type: array - constraints: - type: string displayName: type: string - forceRedeploy: - type: boolean - generation: - type: string metadata: additionalProperties: type: string type: object - properties: - additionalProperties: - type: string - type: object - reconciliationPolicy: - description: Optional ReconcilicationPolicy to specify how target - controller should reconcile. Now only periodic reconciliation is - supported. If the interval is 0, it will only reconcile when the - instance is created or updated. - properties: - interval: - type: string - state: - type: string - required: - - state - type: object - scope: - type: string - topologies: - items: - description: TopologySpec defines the desired device topology the - instance - properties: - bindings: - items: - properties: - config: - additionalProperties: - type: string - type: object - provider: - type: string - role: - type: string - required: - - provider - - role - type: object - type: array - device: - type: string - selector: - additionalProperties: - type: string - type: object - type: object - type: array type: object status: + description: VersionedTargetStatus defines the observed state of Target properties: lastModified: format: date-time @@ -1419,6 +2566,8 @@ spec: properties: additionalProperties: type: string + description: 'Important: Run "make" to regenerate code after modifying + this file' type: object provisioningStatus: description: Defines the state of the ARM resource for long running @@ -1899,6 +3048,136 @@ rules: - get - patch - update +- apiGroups: + - workflow.symphony + resources: + - campaigncontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - workflow.symphony + resources: + - campaigncontainers/finalizers + verbs: + - update +- apiGroups: + - workflow.symphony + resources: + - campaigncontainers/status + verbs: + - get + - patch + - update +- apiGroups: + - solution.symphony + resources: + - instancecontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony + resources: + - instancecontainers/finalizers + verbs: + - update +- apiGroups: + - solution.symphony + resources: + - instancecontainers/status + verbs: + - get + - patch + - update +- apiGroups: + - solution.symphony + resources: + - solutioncontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - solution.symphony + resources: + - solutioncontainers/finalizers + verbs: + - update +- apiGroups: + - solution.symphony + resources: + - solutioncontainers/status + verbs: + - get + - patch + - update +- apiGroups: + - fabric.symphony + resources: + - targetcontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - fabric.symphony + resources: + - targetcontainers/finalizers + verbs: + - update +- apiGroups: + - fabric.symphony + resources: + - targetcontainers/status + verbs: + - get + - patch + - update +- apiGroups: + - federation.symphony + resources: + - catalogcontainers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - federation.symphony + resources: + - catalogcontainers/finalizers + verbs: + - update +- apiGroups: + - federation.symphony + resources: + - catalogcontainers/status + verbs: + - get + - patch + - update --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -2068,6 +3347,15 @@ spec: value: '{{ .Chart.AppVersion }}' - name: CONFIG_NAME value: '{{ include "symphony.fullname" . }}-manager-config' + - name: SERVICE_ACCOUNT_NAME + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName + - name: USE_SERVICE_ACCOUNT_TOKENS + value: "true" + envFrom: + - configMapRef: + name: '{{ include "symphony.envConfigName" . }}' image: '{{ .Values.symphonyImage.repository }}:{{ .Values.symphonyImage.tag }}' imagePullPolicy: '{{ .Values.symphonyImage.pullPolicy }}' @@ -2098,6 +3386,11 @@ spec: securityContext: allowPrivilegeEscalation: false volumeMounts: + - mountPath: /var/run/secrets/tokens + name: symphony-api-token + - mountPath: '{{ include "symphony.apiServingCertsDir" . }}' + name: api-ca-cert + readOnly: true - mountPath: /tmp/k8s-webhook-server/serving-certs name: cert readOnly: true @@ -2128,6 +3421,20 @@ spec: secret: defaultMode: 420 secretName: '{{ include "symphony.fullname" . }}-webhook-server-cert' + - name: symphony-api-token + projected: + sources: + - serviceAccountToken: + audience: '{{ include "symphony.url" . }}' + expirationSeconds: 600 + path: symphony-api-token + - name: api-ca-cert + secret: + defaultMode: 420 + items: + - key: ca.crt + path: ca.crt + secretName: '{{ include "symphony.apiServingCertName" . }}' --- apiVersion: cert-manager.io/v1 kind: Certificate diff --git a/packages/helm/symphony/templates/symphony-service.yaml b/packages/helm/symphony/templates/symphony-service.yaml deleted file mode 100644 index 7d67d936f..000000000 --- a/packages/helm/symphony/templates/symphony-service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "symphony.fullname" . }}-service - namespace: {{ .Release.Namespace }} -spec: - selector: - app: symphony-api - type: ClusterIP - ports: - - protocol: TCP - port: 8080 - targetPort: 8080 - name: http - - protocol: TCP - port: 8081 - targetPort: 8081 - name: https \ No newline at end of file diff --git a/packages/helm/symphony/values.yaml b/packages/helm/symphony/values.yaml index 5fb3095d0..d02981f21 100644 --- a/packages/helm/symphony/values.yaml +++ b/packages/helm/symphony/values.yaml @@ -12,7 +12,7 @@ installServiceExt: true global: azure: identity: - enabled: false + isEnabled: false observability: tracing: exporter: @@ -27,8 +27,25 @@ redis: image: redis/redis-stack-server:latest port: 6379 parent: - url: + url: username: admin password: siteId: hq -imagePrivateRegistryUrl: ghcr.io \ No newline at end of file +imagePrivateRegistryUrl: ghcr.io +api: + apiContainerPortHttp: 8080 + apiContainerPortHttps: 8081 +symphony: + incluster: + httpsport: 8081 + httpport: 8080 + extension: + httpsport: 8081 + httpport: 8080 +Azure: + proxySettings: + isProxyEnabled: false + httpProxy: "" + httpsProxy: "" + noProxy: "" + proxyCert: "" diff --git a/packages/testutils/.gitignore b/packages/testutils/.gitignore new file mode 100644 index 000000000..efa6632a6 --- /dev/null +++ b/packages/testutils/.gitignore @@ -0,0 +1 @@ +bin/* \ No newline at end of file diff --git a/packages/testutils/.vscode/settings.json b/packages/testutils/.vscode/settings.json new file mode 100644 index 000000000..c5dd2ba28 --- /dev/null +++ b/packages/testutils/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "go.buildTags": "mage" +} \ No newline at end of file diff --git a/packages/testutils/README.md b/packages/testutils/README.md new file mode 100644 index 000000000..ce173301b --- /dev/null +++ b/packages/testutils/README.md @@ -0,0 +1,136 @@ + + +# testutils + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils" +``` + +Package testutils provides utilities for testing. It provides 2 main interfaces: + +- Condition: a way to define conditions for an expectation +- Expectation: a way to define expectations for a resource + +This library also includes a set of predefined conditions and expectations for most common use cases. + +### Conditions + +The condition package comes with a few implementations of the Condition interface. Some of the most important constructors are: + +- CountCondition: Creates a condition that checks if the count of a resource is equal to a given value. Works for slices and maps. +- JqCondition: Creates a condition that checks if the result of a jq expression is valid for the resource\(s\). +- JsonPathCondition: Creates a condition that checks if the result of a jsonpath expression is valid for the resource\(s\). +- AllCondition: Creates a condition that checks if all of the given conditions are satisfied. +- AnyCondition: Creates a condition that checks if any of the given conditions is satisfied. + +These conditions can be used to create more complex conditions. For example, the expectations.kube package has a predefined condition helpers to check pod readiness defined like so: + +``` +var PodReadyCondition types.Condition = conditions.All( + conditions.NewKubernetesStatusCondition("Ready", true), + conditions.NewKubernetesStatusCondition("Initialized", true), + conditions.NewKubernetesStatusCondition("ContainersReady", true), +) +``` + +It uses the NewKubernetesStatusCondition constructor \(which itself uses the jsonpath condition constructor\) to create a conditions that checks if the status of a pod is equal to a given value. Then it combines all of these conditions using the AllCondition constructor. + +### Expectations + +The epectation package comes with 4 implementations of the Expectation interface: + +- KubernetesExpectation: an expectation for kubernetes resources +- HelmExpectation: an expectation for helm releases +- AllExpectation: an expectation for grouping multiple expectations and checking if all of them are satisfied +- AnyExpectation: an expectation for grouping multiple expectations and checking if any of them is satisfied + +### KubernetesExpectation + +The KubernetesExpectation is an expectation for kubernetes resources. It is satisfied if the resource is present or not present in the cluster \(depending on its configuration\). The main constructor of this expectation \`kube.Resource\` has 3 required parameters: + +- pattern: a regex pattern string that matches the name of the expected resource\(s\) +- namespace: the namespace of the expected resource\(s\). This is parameter is ignored if the resource is cluster\-scoped. If this resource should be matched in all namespaces, use the "\*" wildcard. +- gvk: the group, version, kind of the expected resource\(s\) + +The expectation also accepts a list of options that can be used to configure the expectation. See the options section of the package documentation for more details. + +Because some resources are commonly expected in the cluster, this package also provides a set of predefined expectations and constructors for them: + +- kube.AbsentResource: a constructor for an expectation for a resource that is not present in the cluster. ie: a resource that has been deleted or has a count of 0. +- kube.Pod: an expectation for a pod\(s\) in the cluster +- kube.AbsentPod: an expectation for a pod\(s\) that is not present in the cluster +- kube.Target: an expectation for a target\(s\) in the cluster +- kube.Solution: an expectation for a solution\(s\) in the cluster +- kube.Instance: an expectation for an instance\(s\) in the cluster + +### HelmExpectation + +The HelmExpectation is an expectation for helm releases. It is satisfied if the release is present or not present in the cluster \(depending on its configuration\). The main constructor of this expectation \`helm.New\` has 2 required parameters: + +- pattern: a regex pattern string that matches the name of the expected release\(s\) +- namespace: the namespace of the expected release\(s\). To match releases in all namespaces, use the "\*" wildcard. + +The expectation also accepts a list of options that can be used to configure the expectation. See the options section of the package documentation for more details. + +### AllExpectation + +The AllExpectation is an expectation for grouping multiple expectations and checking if all of them are satisfied. The main constructor of this expectation \`expectations.All\` accepts a list of expectations as parameters. + +### AnyExpectation + +The AnyExpectation is an expectation for grouping multiple expectations and checking if any of them is satisfied. The main constructor of this expectation \`expectations.Any\` accepts a list of expectations as parameters. + +### Examples + +Check if a pod named "my\-pod\-34jfk3\-fd4k56g" in namespace "default" exists in the cluster: + +``` +exp := kube.Must(kube.Pod("my-pod-34jfk3-fd4k56g", "default")) +if err := exp.Verify(ctx); err != nil { + // expectation failed. handle error +} +``` + +Check if there are 2 pods with prefix "my\-pod\-" in namespace "default" in the cluster and they are ready: + +``` +exp := kube.Must(kube.Pod( + "my-pod-.*", + "default", + kube.WithListCondition( + conditions.Count(2) + ), + kube.WithCondition(kube.PodReadyCondition), // PodReadyCondition is pre-defined in the kube package +)) +if err := exp.Verify(ctx); err != nil { + // expectation failed. handle error +} +``` + +Check if there are 2 pods with prefix "my\-pod\-" in namespace "default" in the cluster and that each pod is ready or initialized and each pod has a specific label + +``` +exp := kube.Must(kube.Pod( + "my-pod-.*", + "default", + kube.WithListCondition( + conditions.Count(2) + ), + kube.WithCondition(conditions.All( + conditions.Any( + conditions.NewKubernetesStatusCondition("Ready", true), + conditions.NewKubernetesStatusCondition("Initialized", true), + ), + kube.NewLabelMatchCondition("my-label", "my-value"), + )), +)) +if err := exp.Verify(ctx); err != nil { + // expectation failed. handle error +} +``` + +## Index + + + +Generated by [gomarkdoc]() diff --git a/packages/testutils/conditions/README.md b/packages/testutils/conditions/README.md new file mode 100644 index 000000000..46edd94f6 --- /dev/null +++ b/packages/testutils/conditions/README.md @@ -0,0 +1,137 @@ + + +# conditions + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils/conditions" +``` + +## Index + +- [type AllCondition](<#AllCondition>) + - [func All\(conditions ...types.Condition\) \*AllCondition](<#All>) + - [func \(a \*AllCondition\) And\(conditions ...types.Condition\) types.Condition](<#AllCondition.And>) + - [func \(a \*AllCondition\) Description\(\) string](<#AllCondition.Description>) + - [func \(a \*AllCondition\) Id\(\) string](<#AllCondition.Id>) + - [func \(c \*AllCondition\) IsSatisfiedBy\(oc context.Context, resource interface\{\}\) error](<#AllCondition.IsSatisfiedBy>) + - [func \(a \*AllCondition\) WithCaching\(\) \*AllCondition](<#AllCondition.WithCaching>) +- [type AnyCondition](<#AnyCondition>) + - [func Any\(conditions ...types.Condition\) \*AnyCondition](<#Any>) + - [func \(a \*AnyCondition\) Description\(\) string](<#AnyCondition.Description>) + - [func \(a \*AnyCondition\) Id\(\) string](<#AnyCondition.Id>) + - [func \(c \*AnyCondition\) IsSatisfiedBy\(oc context.Context, resource interface\{\}\) error](<#AnyCondition.IsSatisfiedBy>) + + + +## type [AllCondition]() + + + +```go +type AllCondition struct { + // contains filtered or unexported fields +} +``` + + +### func [All]() + +```go +func All(conditions ...types.Condition) *AllCondition +``` + +All returns a new AllCondition. + + +### func \(\*AllCondition\) [And]() + +```go +func (a *AllCondition) And(conditions ...types.Condition) types.Condition +``` + +For internal use only. + + +### func \(\*AllCondition\) [Description]() + +```go +func (a *AllCondition) Description() string +``` + +Description implements types.Condition. + + +### func \(\*AllCondition\) [Id]() + +```go +func (a *AllCondition) Id() string +``` + +Id implements types.Condition. + + +### func \(\*AllCondition\) [IsSatisfiedBy]() + +```go +func (c *AllCondition) IsSatisfiedBy(oc context.Context, resource interface{}) error +``` + +IsSatisfiedBy implements types.Condition. + + +### func \(\*AllCondition\) [WithCaching]() + +```go +func (a *AllCondition) WithCaching() *AllCondition +``` + +WithCaching returns a new AllCondition with caching enabled. This means that if a condition is satisfied, it will not be checked again. + + +## type [AnyCondition]() + + + +```go +type AnyCondition struct { + // contains filtered or unexported fields +} +``` + + +### func [Any]() + +```go +func Any(conditions ...types.Condition) *AnyCondition +``` + +Any returns a condition that is satisfied if any of the given conditions are satisfied. + + +### func \(\*AnyCondition\) [Description]() + +```go +func (a *AnyCondition) Description() string +``` + +Description implements types.Condition. + + +### func \(\*AnyCondition\) [Id]() + +```go +func (a *AnyCondition) Id() string +``` + +Id implements types.Condition. + + +### func \(\*AnyCondition\) [IsSatisfiedBy]() + +```go +func (c *AnyCondition) IsSatisfiedBy(oc context.Context, resource interface{}) error +``` + +IsSatisfiedBy implements types.Condition. + +Generated by [gomarkdoc]() diff --git a/packages/testutils/conditions/all.go b/packages/testutils/conditions/all.go new file mode 100644 index 000000000..632a2996f --- /dev/null +++ b/packages/testutils/conditions/all.go @@ -0,0 +1,90 @@ +package conditions + +import ( + "context" + "fmt" + "strings" + + ectx "github.com/eclipse-symphony/symphony/packages/testutils/internal/context" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/google/uuid" +) + +type ( + AllCondition struct { + id string + conditions []types.Condition + successCache map[string]struct{} + shouldCache bool + level int + } +) + +var ( + _ types.Condition = &AllCondition{} +) + +// Description implements types.Condition. +func (a *AllCondition) Description() string { + return "all group" +} + +// Id implements types.Condition. +func (a *AllCondition) Id() string { + return a.id +} + +// IsSatisfiedBy implements types.Condition. +func (c *AllCondition) IsSatisfiedBy(oc context.Context, resource interface{}) error { + ctx := ectx.From(oc) + c.level = ctx.Level() + c.log("checking if all conditions are satisfied") + for i, condition := range c.conditions { + if _, ok := c.successCache[condition.Id()]; c.shouldCache && ok { + c.log("condition %d of %d was satisfied (cached) [%s]: skipping...", i+1, len(c.conditions), condition.Description()) + continue + } + c.log("checking condition %d of %d: [%s]", i+1, len(c.conditions), condition.Description()) + if err := condition.IsSatisfiedBy(ctx.Nested(), resource); err != nil { + c.log("condition %d of %d failed: %s", i+1, len(c.conditions), err) + return err + } + c.log("condition %d of %d was satisfied: [%s]", i+1, len(c.conditions), condition.Description()) + if c.shouldCache { + c.successCache[condition.Id()] = struct{}{} + } + } + c.log("all conditions were satisfied") + return nil +} + +func (c *AllCondition) log(str string, args ...interface{}) { + s := fmt.Sprintf(str, args...) + logger.GetDefaultLogger()("%s[%s]: %s\n", strings.Repeat(" ", c.level), c.Description(), s) +} + +// All returns a new AllCondition. +func All(conditions ...types.Condition) *AllCondition { + return &AllCondition{ + conditions: conditions, + successCache: make(map[string]struct{}), + id: uuid.NewString(), + } +} + +// WithCaching returns a new AllCondition with caching enabled. This means that +// if a condition is satisfied, it will not be checked again. +func (a *AllCondition) WithCaching() *AllCondition { + na := All(a.conditions...) + na.shouldCache = true + return na +} + +// For internal use only. +func (a *AllCondition) And(conditions ...types.Condition) types.Condition { + na := All(a.conditions...) + na.conditions = append(na.conditions, conditions...) + na.shouldCache = a.shouldCache + return na +} diff --git a/packages/testutils/conditions/any.go b/packages/testutils/conditions/any.go new file mode 100644 index 000000000..5675f6a39 --- /dev/null +++ b/packages/testutils/conditions/any.go @@ -0,0 +1,63 @@ +package conditions + +import ( + "context" + "fmt" + "strings" + + ectx "github.com/eclipse-symphony/symphony/packages/testutils/internal/context" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/google/uuid" +) + +type ( + AnyCondition struct { + id string + conditions []types.Condition + level int + } +) + +var ( + _ types.Condition = &AnyCondition{} +) + +// Description implements types.Condition. +func (a *AnyCondition) Description() string { + return "any group" +} + +// Id implements types.Condition. +func (a *AnyCondition) Id() string { + return a.id +} + +// IsSatisfiedBy implements types.Condition. +func (c *AnyCondition) IsSatisfiedBy(oc context.Context, resource interface{}) error { + ctx := ectx.From(oc) + c.level = ctx.Level() + c.log("checking if any condition is satisfied") + for i, condition := range c.conditions { + c.log("checking condition %d of %d: [%s]", i+1, len(c.conditions), condition.Description()) + if err := condition.IsSatisfiedBy(ctx.Nested(), resource); err == nil { + c.log("condition %d of %d was satisfied: [%s]", i+1, len(c.conditions), condition.Description()) + return nil + } + } + return fmt.Errorf("none of the conditions were satisfied") +} + +func (c *AnyCondition) log(str string, args ...interface{}) { + s := fmt.Sprintf(str, args...) + logger.GetDefaultLogger()("%s[%s]: %s\n", strings.Repeat(" ", c.level), c.Description(), s) +} + +// Any returns a condition that is satisfied if any of the given conditions are satisfied. +func Any(conditions ...types.Condition) *AnyCondition { + return &AnyCondition{ + conditions: conditions, + id: uuid.NewString(), + } +} diff --git a/packages/testutils/conditions/basic.go b/packages/testutils/conditions/basic.go new file mode 100644 index 000000000..e83d6f818 --- /dev/null +++ b/packages/testutils/conditions/basic.go @@ -0,0 +1,74 @@ +package conditions + +import ( + "context" + "fmt" + + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/google/uuid" +) + +type ( + basic struct { + id string + desc string + fn func(context.Context, interface{}) error + failMsg func(interface{}, error) string + } + basicopts func(*basic) +) + +var ( + _ types.Condition = basic{} +) + +// IsSatisfiedBy implements types.Condition. +func (b basic) IsSatisfiedBy(c context.Context, resource interface{}) error { + if err := b.fn(c, resource); err != nil { + return fmt.Errorf("%s", b.failMsg(resource, err)) + } + return nil +} + +// Id implements types.Condition. +func (ec basic) Id() string { + return ec.id +} + +// Description implements types.Condition. +func (ec basic) Description() string { + if ec.desc == "" { + return "basic condition" + } + return ec.desc +} + +// WithBasicDescription sets the description of the condition. +func WithBasicDescription(desc string) basicopts { + return func(b *basic) { + b.desc = desc + } +} + +// WithBasicFailureMessage sets the failure message of the condition. +func WithBasicFailureMessage(msg func(interface{}, error) string) basicopts { + return func(b *basic) { + b.failMsg = msg + } +} + +// Basic returns a basic condition. +func Basic(fn func(context.Context, interface{}) error, opts ...basicopts) basic { + b := basic{ + id: uuid.New().String(), + desc: "", + fn: fn, + failMsg: func(i interface{}, e error) string { return fmt.Sprintf("condition failed: %s", e.Error()) }, + } + + for _, opt := range opts { + opt(&b) + } + + return b +} diff --git a/packages/testutils/conditions/condition_test.go b/packages/testutils/conditions/condition_test.go new file mode 100644 index 000000000..6769f6194 --- /dev/null +++ b/packages/testutils/conditions/condition_test.go @@ -0,0 +1,310 @@ +package conditions + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/stretchr/testify/require" +) + +type ( + basicConditionTest struct { + name string + condition types.Condition + resource interface{} + wantErr bool + } + mmap = map[string]string +) + +func TestAll(t *testing.T) { + tests := []basicConditionTest{ + { + name: "should not return error if all conditions are satisfied - 1 right", + condition: All(Count(2)), + resource: []mmap{ + {"id": "1"}, + {"id": "2"}, + }, + }, + { + name: "should not return error if all conditions are satisfied - 2 right", + condition: All(Count(2), Count(2)), + resource: []mmap{ + {"id": "1"}, + {"id": "2"}, + }, + }, + { + name: "should return an error if any condition is not satisfied - 1 wrong", + condition: All(Count(2)), + resource: []mmap{ + {"id": "1"}, + }, + wantErr: true, + }, + { + name: "should return an error if any condition is not satisfied - 2 wrong", + condition: All(Count(2), Count(2)), + resource: []mmap{ + {"id": "1"}, + }, + wantErr: true, + }, + { + name: "should return an error if all conditions are not satisfied - 1 right, 1 wrong", + condition: All(Count(5), Count(1)), + resource: []mmap{ + {"id": "1"}, + }, + wantErr: true, + }, + { + name: "should return an error when any condition is not satisfied - nested", + condition: All( + All(Count(2)), + All( + Count(2), + Count(2), + ), + All( + Count(2), + All( + Count(1), // this one fails + ), + ), + ), + resource: []mmap{ + {"id": "1"}, + {"id": "1"}, + }, + wantErr: true, + }, + { + name: "should return not return error when all conditions are satisfied - nested", + condition: All( + All(Count(2)), + All( + Count(2), + Count(2), + ), + All( + Count(2), + All( + Count(2), + ), + ), + ), + resource: []mmap{ + {"id": "1"}, + {"id": "1"}, + }, + }, + } + testBasicConditions(t, tests) +} + +func TestBasicCondition(t *testing.T) { + cases := []basicConditionTest{ + { + name: "basic condition test", + condition: Basic(func(ctx context.Context, resource interface{}) error { + return nil + }), + }, + { + name: "basic condition test fail", + condition: Basic(func(ctx context.Context, resource interface{}) error { + return errors.New("fail") + }), + wantErr: true, + }, + } + testBasicConditions(t, cases) +} + +func TestBasicConditionWithOptions(t *testing.T) { + b := Basic( + func(ctx context.Context, resource interface{}) error { + r := resource.([]mmap) + if len(r) == 1 { + return nil + } + return errors.New("fail") + }, + WithBasicDescription("test description"), + WithBasicFailureMessage(func(resource interface{}, err error) string { + return "test failure message" + }), + ) + + require.Equal(t, "test description", b.Description()) + require.Equal(t, "test failure message", b.failMsg(nil, nil)) + +} +func TestAny(t *testing.T) { + tests := []basicConditionTest{ + { + name: "should not return error if any condition is satisfied - 1 right", + condition: Any(Count(1)), + resource: []mmap{ + {"id": "1"}, + }, + }, + { + name: "should not return error if any condition is satisfied - 2 right", + condition: Any(Count(1), Count(1)), + resource: []mmap{ + {"id": "1"}, + }, + }, + { + name: "should return an error if no condition is satisfied - 1 wrong", + condition: Any(Count(2)), + resource: []mmap{ + {"id": "1"}, + }, + wantErr: true, + }, + { + name: "should return an error if no condition is satisfied - 2 wrong", + condition: Any(Count(3), Count(2)), + resource: []mmap{ + {"id": "1"}, + }, + wantErr: true, + }, + { + name: "should not return an error if any condition is satisfied - 1 right, 1 wrong", + condition: Any(Count(1), Count(2)), + resource: []mmap{ + {"id": "1"}, + }, + }, + } + testBasicConditions(t, tests) +} + +func TestExpectedCount(t *testing.T) { + tests := []basicConditionTest{ + { + name: "should not return error if count is satisfied", + condition: Count(1), + resource: []mmap{ + {"id": "1"}, + }, + }, + { + name: "should return error if count is not satisfied", + condition: Count(2), + resource: []mmap{ + {"id": "1"}, + }, + wantErr: true, + }, + { + name: "should return error if expected type is not a slice, map or arry", + condition: Count(1), + resource: 1, + wantErr: true, + }, + } + + testBasicConditions(t, tests) +} +func TestCombo(t *testing.T) { + tests := []basicConditionTest{ + { + name: "combo - nested", + condition: Any( + Any(Count(2)), //false + Any( + Count(2), // false + Count(2), // false + ), // false + All( + Count(1), // true + Any( + Count(2), // false + Count(1), // true + ), // true + ), // true + ), // true + resource: []mmap{ + {"id": "1"}, + }, + }, + { + name: "should return error for combo", + condition: Any( + Any(Count(2)), //false + All( + Count(2), // false + Count(1), // true + GreaterThan(1), // false + ), // false + Any( + Count(2), // false + Any( + Count(2), // false + ), // false + All( + GreaterThan(1), // false + Count(1), // true + ), // false + ), // false + ), // false + resource: []mmap{ + {"id": "1"}, + }, + wantErr: true, + }, + } + testBasicConditions(t, tests) +} + +func TestExtendAllAndWithMoreConditions(t *testing.T) { + firstAll := All(Count(1)) + secondAll := firstAll.And(Count(1)) + + require.NotEqual(t, firstAll, secondAll) +} + +func TestAllWithCache(t *testing.T) { + callCount := 0 + c := All( + CountComparator(func(c int) bool { + callCount++ + return c == 1 + }, ""), // this will allways succeed + Count(0), // this will allways fail + ).WithCaching() + resource := []mmap{{"id": "1"}} + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() + + require.Equal(t, 0, callCount) + require.Error(t, c.IsSatisfiedBy(ctx, resource)) + require.Equal(t, 1, callCount) + + require.Error(t, c.IsSatisfiedBy(ctx, resource)) + require.Equal(t, 1, callCount) +} + +func testBasicConditions(t *testing.T, tt []basicConditionTest) { + for _, tt := range tt { + t.Run(tt.name, func(t *testing.T) { + require.NotEmpty(t, tt.condition.Id()) + require.NotEmpty(t, tt.condition.Description()) + err := tt.condition.IsSatisfiedBy(context.Background(), tt.resource) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/packages/testutils/conditions/count.go b/packages/testutils/conditions/count.go new file mode 100644 index 000000000..f519e5a42 --- /dev/null +++ b/packages/testutils/conditions/count.go @@ -0,0 +1,39 @@ +package conditions + +import ( + "context" + "fmt" + + "reflect" +) + +// CountComparator returns a condition that checks the count of a countable +// based on the given comparator function. +func CountComparator(fn func(int) bool, failMessage string) basic { + b := Basic(func(ctx context.Context, i interface{}) error { + switch reflect.TypeOf(i).Kind() { + case reflect.Slice, reflect.Array, reflect.Map, reflect.String: + if !fn(reflect.ValueOf(i).Len()) { + return fmt.Errorf(failMessage) + } + default: + return fmt.Errorf("expected countable, got %T", i) + } + return nil + }) + return b +} + +// Count returns a condition that checks the count of a countable. +func Count(count int) basic { + return CountComparator(func(c int) bool { + return c == count + }, fmt.Sprintf("expected count %d", count)) +} + +// GreaterThan returns a condition that checks the count of a countable. +func GreaterThan(count int) basic { + return CountComparator(func(c int) bool { + return c > count + }, fmt.Sprintf("expected count greater than %d", count)) +} diff --git a/packages/testutils/conditions/jq/README.md b/packages/testutils/conditions/jq/README.md new file mode 100644 index 000000000..c156dd8ab --- /dev/null +++ b/packages/testutils/conditions/jq/README.md @@ -0,0 +1,135 @@ + + +# jq + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils/conditions/jq" +``` + +## Index + +- [type JqCondition](<#JqCondition>) + - [func Equality\(path string, value interface\{\}, opts ...Option\) \*JqCondition](<#Equality>) + - [func MustNew\(path string, opts ...Option\) \*JqCondition](<#MustNew>) + - [func New\(path string, opts ...Option\) \(\*JqCondition, error\)](<#New>) + - [func \(j \*JqCondition\) Description\(\) string](<#JqCondition.Description>) + - [func \(j \*JqCondition\) Id\(\) string](<#JqCondition.Id>) + - [func \(j \*JqCondition\) IsSatisfiedBy\(c context.Context, resource interface\{\}\) error](<#JqCondition.IsSatisfiedBy>) +- [type Option](<#Option>) + - [func WithCustomMatcher\(matcher func\(ctx context.Context, value, root interface\{\}, log logger.Logger\) error\) Option](<#WithCustomMatcher>) + - [func WithDescription\(description string\) Option](<#WithDescription>) + - [func WithLogger\(log func\(format string, args ...interface\{\}\)\) Option](<#WithLogger>) + - [func WithValue\(value interface\{\}\) Option](<#WithValue>) + + + +## type [JqCondition]() + + + +```go +type JqCondition struct { + // contains filtered or unexported fields +} +``` + + +### func [Equality]() + +```go +func Equality(path string, value interface{}, opts ...Option) *JqCondition +``` + +Equality returns a new JqCondition that checks if the value at the given path resolves to the given value exactly. + + +### func [MustNew]() + +```go +func MustNew(path string, opts ...Option) *JqCondition +``` + +MustNew returns a new JqCondition. It panics if the condition cannot be created. + + +### func [New]() + +```go +func New(path string, opts ...Option) (*JqCondition, error) +``` + +New returns a new JqCondition. + + +### func \(\*JqCondition\) [Description]() + +```go +func (j *JqCondition) Description() string +``` + +Description implements condition.Condition. + + +### func \(\*JqCondition\) [Id]() + +```go +func (j *JqCondition) Id() string +``` + +Id implements condition.Condition. + + +### func \(\*JqCondition\) [IsSatisfiedBy]() + +```go +func (j *JqCondition) IsSatisfiedBy(c context.Context, resource interface{}) error +``` + +IsSatisfiedBy implements condition.Condition. + + +## type [Option]() + + + +```go +type Option func(*JqCondition) +``` + + +### func [WithCustomMatcher]() + +```go +func WithCustomMatcher(matcher func(ctx context.Context, value, root interface{}, log logger.Logger) error) Option +``` + +WithCustomMatcher specifies the matcher to be used to match the jq result. + + +### func [WithDescription]() + +```go +func WithDescription(description string) Option +``` + +WithDescription specifies the description of the condition. + + +### func [WithLogger]() + +```go +func WithLogger(log func(format string, args ...interface{})) Option +``` + +WithLogger specifies the logger to be used to log the jq operations. + + +### func [WithValue]() + +```go +func WithValue(value interface{}) Option +``` + +WithValue does an equality check on the jq result. + +Generated by [gomarkdoc]() diff --git a/packages/testutils/conditions/jq/common.go b/packages/testutils/conditions/jq/common.go new file mode 100644 index 000000000..118793993 --- /dev/null +++ b/packages/testutils/conditions/jq/common.go @@ -0,0 +1,6 @@ +package jq + +// Equality returns a new JqCondition that checks if the value at the given path resolves to the given value exactly. +func Equality(path string, value interface{}, opts ...Option) *JqCondition { + return MustNew(path, append(opts, WithValue(value))...) +} diff --git a/packages/testutils/conditions/jq/jq.go b/packages/testutils/conditions/jq/jq.go new file mode 100644 index 000000000..c91670e09 --- /dev/null +++ b/packages/testutils/conditions/jq/jq.go @@ -0,0 +1,167 @@ +package jq + +import ( + "context" + "fmt" + "reflect" + "strings" + + ectx "github.com/eclipse-symphony/symphony/packages/testutils/internal/context" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/google/uuid" + "github.com/itchyny/gojq" +) + +type ( + JqCondition struct { + id string + matcher func(ctx context.Context, values, root interface{}, log logger.Logger) error + jq *gojq.Query + description string + l func(format string, args ...interface{}) + path string + level int + } + + Option func(*JqCondition) +) + +var ( + _ types.Condition = &JqCondition{} +) + +// WithCustomMatcher specifies the matcher to be used to match the jq result. +func WithCustomMatcher(matcher func(ctx context.Context, value, root interface{}, log logger.Logger) error) Option { + return func(j *JqCondition) { + j.matcher = matcher + } +} + +// WithLogger specifies the logger to be used to log the jq operations. +func WithLogger(log func(format string, args ...interface{})) Option { + return func(j *JqCondition) { + j.l = log + } +} + +// WithValue does an equality check on the jq result. +func WithValue(value interface{}) Option { + return func(j *JqCondition) { + t1 := reflect.TypeOf(value) + j.matcher = func(ctx context.Context, resolved, root interface{}, log logger.Logger) error { + j.log("Comparing %v with %v", value, resolved) + if resolved == nil { + if value == nil { + return nil + } + return fmt.Errorf("expected %v, got nil", value) + } + + if !reflect.TypeOf(resolved).ConvertibleTo(t1) { + return fmt.Errorf("expected %v, got %v", value, resolved) + } + + if reflect.DeepEqual(value, reflect.ValueOf(resolved).Convert(t1).Interface()) { + return nil + } + return fmt.Errorf("expected %v, got %v", value, resolved) + } + } +} + +// WithDescription specifies the description of the condition. +func WithDescription(description string) Option { + return func(j *JqCondition) { + j.description = description + } +} + +// New returns a new JqCondition. +func New(path string, opts ...Option) (*JqCondition, error) { + jqc := JqCondition{ + matcher: defaultMatcher, + path: path, + id: uuid.NewString(), + } + + jq, err := gojq.Parse(path) + if err != nil { + return nil, err + } + jqc.jq = jq + + for _, opt := range opts { + opt(&jqc) + } + + return &jqc, nil +} + +func must(jqc *JqCondition, err error) *JqCondition { + if err != nil { + panic(err) + } + return jqc +} + +// MustNew returns a new JqCondition. It panics if the condition cannot be created. +func MustNew(path string, opts ...Option) *JqCondition { + return must(New(path, opts...)) +} + +// IsSatisfiedBy implements condition.Condition. +func (j *JqCondition) IsSatisfiedBy(c context.Context, resource interface{}) error { + ctx := ectx.From(c) + j.level = ctx.Level() + j.log("Evaluating jq condition on resource") + + iter := j.jq.RunWithContext(ctx, resource) + for { + v, ok := iter.Next() + if !ok { + break + } + if err, ok := v.(error); ok { + return err + } + err := j.matcher(ctx, v, resource, j.log) + if err != nil { + return err + } + } + + return nil +} + +// Id implements condition.Condition. +func (j *JqCondition) Id() string { + return j.id +} + +// Description implements condition.Condition. +func (j *JqCondition) Description() string { + if j.description != "" { + return j.description + } + return j.path +} + +func (j *JqCondition) log(format string, args ...interface{}) { + s := fmt.Sprintf(format, args...) + format = "%s[%s]: %s\n" + args = []interface{}{strings.Repeat(" ", j.level), j.Description(), s} + + if j.l != nil { + j.l(format, args...) + } else { + logger.GetDefaultLogger()(format, args...) + } +} + +func defaultMatcher(ctx context.Context, value, root interface{}, log logger.Logger) error { + if value != nil { + return nil + } + return fmt.Errorf("expected non-empty result, got empty result") +} diff --git a/packages/testutils/conditions/jq/jq_test.go b/packages/testutils/conditions/jq/jq_test.go new file mode 100644 index 000000000..b0146ac7c --- /dev/null +++ b/packages/testutils/conditions/jq/jq_test.go @@ -0,0 +1,169 @@ +package jq + +import ( + "context" + "errors" + "testing" + + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/stretchr/testify/require" +) + +type ( + jqTestCase struct { + name string + j *JqCondition + resource any + wantErr bool + } + mmap = map[string]interface{} +) + +func TestJQCondition_IsSatisfiedBy(t *testing.T) { + var ( + foobar = mmap{ + "foo": mmap{ + "bar": "baz", + "nullValue": nil, + }, + } + basicCases = []jqTestCase{ + { + name: "should return no error if jq query matches value", + j: MustNew(`.foo.bar`, WithValue("baz")), + resource: foobar, + }, + { + name: "should return error if jq query dosesn't match value", + j: MustNew(`.foo.bar`, WithValue("wrong")), + resource: foobar, + wantErr: true, + }, + { + name: "should return no error if jq query matches for custom matcher", + j: MustNew(`.foo.bar == "baz"`, WithCustomMatcher(customTruthyTestMatcher)), + resource: foobar, + }, + { + name: "should return error if jq query does not match for custom matcher", + j: MustNew(`.foo.bar != "baz"`, WithCustomMatcher(customTruthyTestMatcher)), + resource: foobar, + wantErr: true, + }, + { + name: "should return error when path resolves to non-existent and no matcher is specified", + j: MustNew(".foo.bar.nonexistent"), + resource: foobar, + wantErr: true, + }, + { + name: "should return error when path resolves to nil value and no matcher is specified", + j: MustNew(".foo.nullValue"), + resource: foobar, + wantErr: true, + }, + { + name: "should handle nil resource", + j: Equality(".foo.nullValue", nil), + resource: foobar, + }, + { + name: "should handle nil resource and nil wrong value", + j: Equality(".foo.nullValue", "wrong"), + resource: foobar, + wantErr: true, + }, + { + name: "should handle incompativle types", + j: Equality(".foo.bar", 1), + resource: foobar, + wantErr: true, + }, + { + name: "should return no error when path resolves to non nil and no matcher is specified", + j: MustNew(".foo.bar"), + resource: foobar, + }, + { + name: "should accept a custom logger", + j: MustNew(".foo.bar", WithLogger(customTestLogger)), + resource: foobar, + }, + { + name: "should return error for unsupported type", + j: MustNew(".foo.bar", WithValue("baz")), + resource: struct{ name string }{name: "test"}, + wantErr: true, + }, + { + name: "should pass ussing helper function", + j: Equality(".foo.bar", "baz"), + resource: foobar, + }, + { + name: "should fail ussing helper function", + j: Equality(".foo.bar", "wrong"), + resource: foobar, + wantErr: true, + }, + } + ) + for _, tt := range basicCases { + t.Run(tt.name, func(t *testing.T) { + err := tt.j.IsSatisfiedBy(context.Background(), tt.resource) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestHasId(t *testing.T) { + jq := MustNew(`.foo.bar`) + require.NotEmpty(t, jq.Id()) +} + +func TestJQCondition_Description(t *testing.T) { + tests := []struct { + name string + description string + path string + want string + }{ + { + name: "should return description if set", + description: "test description", + path: `.foo == "bar"`, + want: "test description", + }, + { + name: "should return path if description not set", + path: `.foo == "bar"`, + want: `.foo == "bar"`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + j := must(New(tt.path, WithDescription(tt.description))) + + require.Equal(t, tt.want, j.Description()) + }) + } +} + +func TestShouldPanicForInvalidJQ(t *testing.T) { + require.Panics(t, func() { + MustNew(`some invalid jq query`) + }) +} + +func customTruthyTestMatcher(ctx context.Context, value, root interface{}, log logger.Logger) error { + if value.(bool) == true { + return nil + } + return errors.New("jq query did not match") +} + +func customTestLogger(format string, args ...interface{}) {} diff --git a/packages/testutils/conditions/jsonpath/README.md b/packages/testutils/conditions/jsonpath/README.md new file mode 100644 index 000000000..d993a5d23 --- /dev/null +++ b/packages/testutils/conditions/jsonpath/README.md @@ -0,0 +1,125 @@ + + +# jsonpath + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils/conditions/jsonpath" +``` + +## Index + +- [type JpCondition](<#JpCondition>) + - [func MustNew\(path string, opts ...Option\) \*JpCondition](<#MustNew>) + - [func New\(path string, opts ...Option\) \(\*JpCondition, error\)](<#New>) + - [func \(j \*JpCondition\) Description\(\) string](<#JpCondition.Description>) + - [func \(j \*JpCondition\) Id\(\) string](<#JpCondition.Id>) + - [func \(j \*JpCondition\) IsSatisfiedBy\(c context.Context, resource interface\{\}\) error](<#JpCondition.IsSatisfiedBy>) +- [type Option](<#Option>) + - [func WithCustomMatcher\(matcher func\(ctx context.Context, value, root interface\{\}, log logger.Logger\) error\) Option](<#WithCustomMatcher>) + - [func WithDescription\(description string\) Option](<#WithDescription>) + - [func WithLogger\(log func\(format string, args ...interface\{\}\)\) Option](<#WithLogger>) + - [func WithValue\(value interface\{\}\) Option](<#WithValue>) + + + +## type [JpCondition]() + + + +```go +type JpCondition struct { + // contains filtered or unexported fields +} +``` + + +### func [MustNew]() + +```go +func MustNew(path string, opts ...Option) *JpCondition +``` + +MustNew returns a new JqCondition. It panics if the condition cannot be created. + + +### func [New]() + +```go +func New(path string, opts ...Option) (*JpCondition, error) +``` + +New returns a new JqCondition. + + +### func \(\*JpCondition\) [Description]() + +```go +func (j *JpCondition) Description() string +``` + +Description implements types.Condition. + + +### func \(\*JpCondition\) [Id]() + +```go +func (j *JpCondition) Id() string +``` + +Id implements types.Condition. + + +### func \(\*JpCondition\) [IsSatisfiedBy]() + +```go +func (j *JpCondition) IsSatisfiedBy(c context.Context, resource interface{}) error +``` + +IsSatisfiedBy implements condition.Condition. + + +## type [Option]() + + + +```go +type Option func(*JpCondition) +``` + + +### func [WithCustomMatcher]() + +```go +func WithCustomMatcher(matcher func(ctx context.Context, value, root interface{}, log logger.Logger) error) Option +``` + +WithCustomMatcher specifies the matcher to be used to match the jsonpath result. + + +### func [WithDescription]() + +```go +func WithDescription(description string) Option +``` + +WithDescription sets the description of the condition. + + +### func [WithLogger]() + +```go +func WithLogger(log func(format string, args ...interface{})) Option +``` + +WithLogger specifies the logger to be used to log the jsonpath operations. + + +### func [WithValue]() + +```go +func WithValue(value interface{}) Option +``` + +WithValue does an equality check on the jsonpath result. + +Generated by [gomarkdoc]() diff --git a/packages/testutils/conditions/jsonpath/jsonpath.go b/packages/testutils/conditions/jsonpath/jsonpath.go new file mode 100644 index 000000000..3012ae904 --- /dev/null +++ b/packages/testutils/conditions/jsonpath/jsonpath.go @@ -0,0 +1,143 @@ +package jsonpath + +import ( + "context" + "fmt" + "reflect" + "strings" + + ectx "github.com/eclipse-symphony/symphony/packages/testutils/internal/context" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/google/uuid" + "github.com/oliveagle/jsonpath" +) + +type ( + JpCondition struct { + id string + matcher func(ctx context.Context, values, root interface{}, log logger.Logger) error + jp *jsonpath.Compiled + description string + l func(format string, args ...interface{}) + level int + } + + Option func(*JpCondition) +) + +var ( + _ types.Condition = &JpCondition{} +) + +// WithCustomMatcher specifies the matcher to be used to match the jsonpath result. +func WithCustomMatcher(matcher func(ctx context.Context, value, root interface{}, log logger.Logger) error) Option { + return func(j *JpCondition) { + j.matcher = matcher + } +} + +// WithLogger specifies the logger to be used to log the jsonpath operations. +func WithLogger(log func(format string, args ...interface{})) Option { + return func(j *JpCondition) { + j.l = log + } +} + +// WithValue does an equality check on the jsonpath result. +func WithValue(value interface{}) Option { + return func(j *JpCondition) { + j.matcher = func(ctx context.Context, resolved, root interface{}, log logger.Logger) error { + j.log("Comparing %v with %s", value, resolved) + + if reflect.DeepEqual(value, resolved) { + return nil + } + return fmt.Errorf("expected %v, got %v", value, resolved) + } + } +} + +// WithDescription sets the description of the condition. +func WithDescription(description string) Option { + return func(j *JpCondition) { + j.description = description + } +} + +// New returns a new JqCondition. +func New(path string, opts ...Option) (*JpCondition, error) { + jpc := JpCondition{ + matcher: defaultMatcher, + id: uuid.NewString(), + } + + jp, err := jsonpath.Compile(path) + if err != nil { + return nil, err + } + jpc.jp = jp + + for _, opt := range opts { + opt(&jpc) + } + + return &jpc, nil +} + +func must(jpc *JpCondition, err error) *JpCondition { + if err != nil { + panic(err) + } + return jpc +} + +// MustNew returns a new JqCondition. It panics if the condition cannot be created. +func MustNew(path string, opts ...Option) *JpCondition { + return must(New(path, opts...)) +} + +// IsSatisfiedBy implements condition.Condition. +func (j *JpCondition) IsSatisfiedBy(c context.Context, resource interface{}) error { + ctx := ectx.From(c) + j.level = ctx.Level() + j.log("Evaluating jsonpath condition on resource") + value, err := j.jp.Lookup(resource) + if err != nil { + return err + } + + return j.matcher(ctx, value, resource, j.log) +} + +// Id implements types.Condition. +func (j *JpCondition) Id() string { + return j.id +} + +// Description implements types.Condition. +func (j *JpCondition) Description() string { + if j.description != "" { + return j.description + } + return j.jp.String() +} + +func defaultMatcher(ctx context.Context, value, root interface{}, log logger.Logger) error { + if value != nil { + return nil + } + return fmt.Errorf("expected non-empty result, got empty result") +} + +func (j *JpCondition) log(format string, args ...interface{}) { + s := fmt.Sprintf(format, args...) + format = "%s[%s]: %s\n" + args = []interface{}{strings.Repeat(" ", j.level), j.Description(), s} + + if j.l != nil { + j.l(format, args...) + } else { + logger.GetDefaultLogger()(format, args...) + } +} diff --git a/packages/testutils/conditions/jsonpath/jsonpath_test.go b/packages/testutils/conditions/jsonpath/jsonpath_test.go new file mode 100644 index 000000000..438abb0ea --- /dev/null +++ b/packages/testutils/conditions/jsonpath/jsonpath_test.go @@ -0,0 +1,149 @@ +package jsonpath + +import ( + "context" + "errors" + "reflect" + "testing" + + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/stretchr/testify/require" +) + +type ( + mmap = map[string]interface{} + basicTestCase struct { + name string + j *JpCondition + resource any + wantErr bool + } +) + +func TestShouldPanicForInvalidJsonPath(t *testing.T) { + require.Panics(t, func() { + MustNew(`some invalid json path`) + }) +} + +func TestIsSatisfiedBy(t *testing.T) { + type Array[T any] []T + + data := mmap{ + "store": mmap{ + "type": "book store", + "books": Array[mmap]{ + { + "title": "The Catcher in the Rye", + "author": "J.D. Salinger", + }, + { + "title": "To Kill a Mockingbird", + "author": "Harper Lee", + }, + }, + }, + } + + tests := []basicTestCase{ + { + name: "should return no error if jsonpath query matches value", + j: MustNew(`$.store.books[0].title`, WithValue("The Catcher in the Rye")), + resource: data, + }, + { + name: "should return error if jsonpath query dosesn't match value", + j: MustNew(`$.store.books[0].title`, WithValue("wrong")), + resource: data, + wantErr: true, + }, + { + name: "should return no error if jsonpath query matches for custom matcher", + j: MustNew(`$.store.books[?(@.author == 'Harper Lee')].author`, WithCustomMatcher(customAuthorMatcher)), + resource: data, + }, + { + name: "should return error if jsonpath query does not match for custom matcher", + j: MustNew(`$.store.books[?(@.author != 'Harper Lee')].author`, WithCustomMatcher(customAuthorMatcher)), + resource: data, + wantErr: true, + }, + { + name: "should return error when path resolves to nil and no matcher is specified", + j: MustNew("$.store.books[0].title.nonexistent"), + resource: data, + wantErr: true, + }, + { + name: "should return no error when path resolves to non nil and no matcher is specified", + j: MustNew("$.store.books[0].title"), + resource: data, + }, + { + name: "should accept a custom logger", + j: MustNew("$.store.books[0].title", WithLogger(customTestLogger)), + resource: data, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.j.IsSatisfiedBy(context.Background(), tt.resource) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestJsonPathCondition_Description(t *testing.T) { + tests := []struct { + name string + description string + path string + want string + }{ + { + name: "should return description if set", + description: "test description", + path: "$.foo.bar", + want: "test description", + }, + { + name: "should return path if description not set", + path: "$.foo.bar", + want: "$.foo.bar", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + j := must(New(tt.path, WithDescription(tt.description))) + + require.Contains(t, j.Description(), tt.want) + }) + } +} + +func TestHasId(t *testing.T) { + jp := MustNew(`$.foo.bar`) + require.NotEmpty(t, jp.Id()) +} + +func customAuthorMatcher(ctx context.Context, value, root interface{}, log logger.Logger) error { + fave := []interface{}{ + "Harper Lee", + } + author, ok := value.([]interface{}) + if !ok { + return errors.New("not an array") + } + if reflect.DeepEqual(author, fave) { + return nil + } + + return errors.New("jsonpath query did not match") +} + +func customTestLogger(format string, args ...interface{}) {} diff --git a/packages/testutils/doc.go b/packages/testutils/doc.go new file mode 100644 index 000000000..28aec9de2 --- /dev/null +++ b/packages/testutils/doc.go @@ -0,0 +1,122 @@ +// Package testutils provides utilities for testing. It provides 2 main interfaces: +// +// - Condition: a way to define conditions for an expectation +// - Expectation: a way to define expectations for a resource +// +// This library also includes a set of predefined conditions and expectations for most common use cases. +// +// # Conditions +// +// The condition package comes with a few implementations of the Condition interface. Some of the most important constructors are: +// - CountCondition: Creates a condition that checks if the count of a resource is equal to a given value. Works for slices and maps. +// - JqCondition: Creates a condition that checks if the result of a jq expression is valid for the resource(s). +// - JsonPathCondition: Creates a condition that checks if the result of a jsonpath expression is valid for the resource(s). +// - AllCondition: Creates a condition that checks if all of the given conditions are satisfied. +// - AnyCondition: Creates a condition that checks if any of the given conditions is satisfied. +// +// These conditions can be used to create more complex conditions. For example, the expectations.kube package has +// a predefined condition helpers to check pod readiness defined like so: +// +// var PodReadyCondition types.Condition = conditions.All( +// conditions.NewKubernetesStatusCondition("Ready", true), +// conditions.NewKubernetesStatusCondition("Initialized", true), +// conditions.NewKubernetesStatusCondition("ContainersReady", true), +// ) +// +// It uses the NewKubernetesStatusCondition constructor (which itself uses the jsonpath condition constructor) +// to create a conditions that checks if the status of a pod is equal to a given value. Then it combines all of these conditions +// using the AllCondition constructor. +// +// # Expectations +// +// The epectation package comes with 4 implementations of the Expectation interface: +// - KubernetesExpectation: an expectation for kubernetes resources +// - HelmExpectation: an expectation for helm releases +// - AllExpectation: an expectation for grouping multiple expectations and checking if all of them are satisfied +// - AnyExpectation: an expectation for grouping multiple expectations and checking if any of them is satisfied +// +// # KubernetesExpectation +// +// The KubernetesExpectation is an expectation for kubernetes resources. +// It is satisfied if the resource is present or not present in the cluster (depending on its configuration). +// The main constructor of this expectation `kube.Resource` has 3 required parameters: +// - pattern: a regex pattern string that matches the name of the expected resource(s) +// - namespace: the namespace of the expected resource(s). This is parameter is ignored if the resource is cluster-scoped. +// If this resource should be matched in all namespaces, use the "*" wildcard. +// - gvk: the group, version, kind of the expected resource(s) +// +// The expectation also accepts a list of options that can be used to configure the expectation. +// See the options section of the package documentation for more details. +// +// Because some resources are commonly expected in the cluster, this package also provides a set of predefined expectations +// and constructors for them: +// - kube.AbsentResource: a constructor for an expectation for a resource that is not present in the cluster. ie: a resource that has been deleted or has a count of 0. +// - kube.Pod: an expectation for a pod(s) in the cluster +// - kube.AbsentPod: an expectation for a pod(s) that is not present in the cluster +// - kube.Target: an expectation for a target(s) in the cluster +// - kube.Solution: an expectation for a solution(s) in the cluster +// - kube.Instance: an expectation for an instance(s) in the cluster +// +// # HelmExpectation +// +// The HelmExpectation is an expectation for helm releases. It is satisfied if the release is present or not present in the cluster (depending on its configuration). +// The main constructor of this expectation `helm.New` has 2 required parameters: +// - pattern: a regex pattern string that matches the name of the expected release(s) +// - namespace: the namespace of the expected release(s). To match releases in all namespaces, use the "*" wildcard. +// +// The expectation also accepts a list of options that can be used to configure the expectation. See the options section of the package documentation for more details. +// +// # AllExpectation +// +// The AllExpectation is an expectation for grouping multiple expectations and checking if all of them are satisfied. +// The main constructor of this expectation `expectations.All` accepts a list of expectations as parameters. +// +// # AnyExpectation +// +// The AnyExpectation is an expectation for grouping multiple expectations and checking if any of them is satisfied. +// The main constructor of this expectation `expectations.Any` accepts a list of expectations as parameters. +// +// # Examples +// +// Check if a pod named "my-pod-34jfk3-fd4k56g" in namespace "default" exists in the cluster: +// +// exp := kube.Must(kube.Pod("my-pod-34jfk3-fd4k56g", "default")) +// if err := exp.Verify(ctx); err != nil { +// // expectation failed. handle error +// } +// +// Check if there are 2 pods with prefix "my-pod-" in namespace "default" in the cluster and they are ready: +// +// exp := kube.Must(kube.Pod( +// "my-pod-.*", +// "default", +// kube.WithListCondition( +// conditions.Count(2) +// ), +// kube.WithCondition(kube.PodReadyCondition), // PodReadyCondition is pre-defined in the kube package +// )) +// if err := exp.Verify(ctx); err != nil { +// // expectation failed. handle error +// } +// +// Check if there are 2 pods with prefix "my-pod-" in namespace "default" in the cluster +// and that each pod is ready or initialized and each pod has a specific label +// +// exp := kube.Must(kube.Pod( +// "my-pod-.*", +// "default", +// kube.WithListCondition( +// conditions.Count(2) +// ), +// kube.WithCondition(conditions.All( +// conditions.Any( +// conditions.NewKubernetesStatusCondition("Ready", true), +// conditions.NewKubernetesStatusCondition("Initialized", true), +// ), +// kube.NewLabelMatchCondition("my-label", "my-value"), +// )), +// )) +// if err := exp.Verify(ctx); err != nil { +// // expectation failed. handle error +// } +package main diff --git a/packages/testutils/expectations/README.md b/packages/testutils/expectations/README.md new file mode 100644 index 000000000..ea39b1dcf --- /dev/null +++ b/packages/testutils/expectations/README.md @@ -0,0 +1,127 @@ + + +# expectations + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils/expectations" +``` + +## Index + +- [type AllExpectation](<#AllExpectation>) + - [func All\(expectations ...types.Expectation\) \*AllExpectation](<#All>) + - [func \(e \*AllExpectation\) Description\(\) string](<#AllExpectation.Description>) + - [func \(e \*AllExpectation\) Id\(\) string](<#AllExpectation.Id>) + - [func \(e \*AllExpectation\) Verify\(c context.Context\) error](<#AllExpectation.Verify>) + - [func \(a \*AllExpectation\) WithCaching\(\) \*AllExpectation](<#AllExpectation.WithCaching>) +- [type AnyExpectation](<#AnyExpectation>) + - [func Any\(expectations ...types.Expectation\) \*AnyExpectation](<#Any>) + - [func \(e \*AnyExpectation\) Description\(\) string](<#AnyExpectation.Description>) + - [func \(e \*AnyExpectation\) Id\(\) string](<#AnyExpectation.Id>) + - [func \(e \*AnyExpectation\) Verify\(c context.Context\) error](<#AnyExpectation.Verify>) + + + +## type [AllExpectation]() + + + +```go +type AllExpectation struct { + // contains filtered or unexported fields +} +``` + + +### func [All]() + +```go +func All(expectations ...types.Expectation) *AllExpectation +``` + +All returns an expectation that is satisfied if all of the given expectations are satisfied. + + +### func \(\*AllExpectation\) [Description]() + +```go +func (e *AllExpectation) Description() string +``` + +Description implements types.Expectation. + + +### func \(\*AllExpectation\) [Id]() + +```go +func (e *AllExpectation) Id() string +``` + +Id implements types.Expectation. + + +### func \(\*AllExpectation\) [Verify]() + +```go +func (e *AllExpectation) Verify(c context.Context) error +``` + +Verify implements types.Expectation. + + +### func \(\*AllExpectation\) [WithCaching]() + +```go +func (a *AllExpectation) WithCaching() *AllExpectation +``` + +WithCaching returns a new expectation that caches the result of each expectation successfull expectation so that it is not verified again in future calls to Verify. + + +## type [AnyExpectation]() + + + +```go +type AnyExpectation struct { + // contains filtered or unexported fields +} +``` + + +### func [Any]() + +```go +func Any(expectations ...types.Expectation) *AnyExpectation +``` + +Any returns an expectation that is satisfied if any of the given expectations is satisfied. + + +### func \(\*AnyExpectation\) [Description]() + +```go +func (e *AnyExpectation) Description() string +``` + +Description implements types.Expectation. + + +### func \(\*AnyExpectation\) [Id]() + +```go +func (e *AnyExpectation) Id() string +``` + +Id implements types.Expectation. + + +### func \(\*AnyExpectation\) [Verify]() + +```go +func (e *AnyExpectation) Verify(c context.Context) error +``` + +Verify implements types.Expectation. + +Generated by [gomarkdoc]() diff --git a/packages/testutils/expectations/expectation.go b/packages/testutils/expectations/expectation.go new file mode 100644 index 000000000..7224d0469 --- /dev/null +++ b/packages/testutils/expectations/expectation.go @@ -0,0 +1,126 @@ +package expectations + +import ( + "context" + "fmt" + "strings" + + econtext "github.com/eclipse-symphony/symphony/packages/testutils/internal/context" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/google/uuid" +) + +type ( + AllExpectation struct { + expectations []types.Expectation + successCache map[string]struct{} + shouldCache bool + level int + id string + } + AnyExpectation struct { + level int + id string + expectations []types.Expectation + } +) + +var ( + _ types.Expectation = &AllExpectation{} + _ types.Expectation = &AnyExpectation{} +) + +// Verify implements types.Expectation. +func (e *AnyExpectation) Verify(c context.Context) error { + ctx := econtext.From(c) + e.level = ctx.Level() + e.log("checking if any expectation is satisfied") + for i, expectation := range e.expectations { + e.log("checking expectation %d of %d: [%s]", i+1, len(e.expectations), expectation.Description()) + if err := expectation.Verify(ctx.Nested()); err == nil { + e.log("expectation %d of %d was satisfied", i+1, len(e.expectations)) + return nil + } + } + return fmt.Errorf("no expectation was satisfied") +} + +// Verify implements types.Expectation. +func (e *AllExpectation) Verify(c context.Context) error { + ctx := econtext.From(c) + e.level = ctx.Level() + e.log("checking if all expectations are satisfied") + for i, expectation := range e.expectations { + if _, ok := e.successCache[expectation.Id()]; ok && e.shouldCache { + e.log("expectation %d of %d was satisfied (cached) [%s]: skipping...", i+1, len(e.expectations), expectation.Description()) + continue + } + e.log("checking expectation %d of %d: [%s]", i+1, len(e.expectations), expectation.Description()) + if err := expectation.Verify(ctx.Nested()); err != nil { + e.log("expectation %d of %d failed: %s", i+1, len(e.expectations), err) + return err + } + e.log("expectation %d of %d was satisfied [%s]", i+1, len(e.expectations), expectation.Description()) + if e.shouldCache { + e.successCache[expectation.Id()] = struct{}{} + } + } + e.log("all expectations were satisfied") + return nil +} + +// Description implements types.Expectation. +func (e *AnyExpectation) Description() string { + return "any expectation" +} + +// Id implements types.Expectation. +func (e *AnyExpectation) Id() string { + return e.id +} + +// Id implements types.Expectation. +func (e *AllExpectation) Id() string { + return e.id +} + +// Description implements types.Expectation. +func (e *AllExpectation) Description() string { + return "all expectation" +} + +// WithCaching returns a new expectation that caches the result of each expectation successfull expectation +// so that it is not verified again in future calls to Verify. +func (a *AllExpectation) WithCaching() *AllExpectation { + na := All(a.expectations...) + na.shouldCache = true + return na +} + +// All returns an expectation that is satisfied if all of the given expectations are satisfied. +func All(expectations ...types.Expectation) *AllExpectation { + return &AllExpectation{ + id: uuid.NewString(), + expectations: expectations, + successCache: make(map[string]struct{}), + } +} + +// Any returns an expectation that is satisfied if any of the given expectations is satisfied. +func Any(expectations ...types.Expectation) *AnyExpectation { + return &AnyExpectation{ + id: uuid.NewString(), + expectations: expectations, + } +} + +func (e *AnyExpectation) log(str string, args ...interface{}) { + s := fmt.Sprintf(str, args...) + logger.GetDefaultLogger()("%s[%s]: %s\n", strings.Repeat(" ", e.level), e.Description(), s) +} + +func (e *AllExpectation) log(str string, args ...interface{}) { + s := fmt.Sprintf(str, args...) + logger.GetDefaultLogger()("%s[%s]: %s\n", strings.Repeat(" ", e.level), e.Description(), s) +} diff --git a/packages/testutils/expectations/expectation_test.go b/packages/testutils/expectations/expectation_test.go new file mode 100644 index 000000000..23e5108e0 --- /dev/null +++ b/packages/testutils/expectations/expectation_test.go @@ -0,0 +1,228 @@ +package expectations + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +type ( + basicConditionTest struct { + name string + expectation types.Expectation + wantErr bool + } +) + +func TestAll(t *testing.T) { + tests := []basicConditionTest{ + { + name: "should not return error if all expectations are satisfied - 1 right", + expectation: All(expectation(true)), + }, + { + name: "should not return error if all expectations are satisfied - 2 right", + expectation: All(expectation(true), expectation(true)), + }, + { + name: "should return an error if any expectation is not satisfied - 1 wrong", + expectation: All(expectation(false)), + wantErr: true, + }, + { + name: "should return an error if any expectation is not satisfied - 2 wrong", + expectation: All(expectation(false), expectation(false)), + wantErr: true, + }, + { + name: "should return an error if all expectations are not satisfied - 1 right, 1 wrong", + expectation: All(expectation(true), expectation(false)), + wantErr: true, + }, + { + name: "should return an error when any expectation is not satisfied - nested", + expectation: All( + All(expectation(true)), + All( + expectation(true), + expectation(true), + ), + All( + expectation(true), + All( + expectation(false), // this one fails + ), + ), + ), + wantErr: true, + }, + { + name: "should return not return error when all expectations are satisfied - nested", + expectation: All( + All(expectation(true)), + All( + expectation(true), + expectation(true), + ), + All( + expectation(true), + All( + expectation(true), + ), + ), + ), + }, + } + testBasicConditions(t, tests) +} + +func TestAny(t *testing.T) { + tests := []basicConditionTest{ + { + name: "should not return error if any expectation is satisfied - 1 right", + expectation: Any(expectation(true)), + }, + { + name: "should not return error if any expectation is satisfied - 2 right", + expectation: Any(expectation(true), expectation(true)), + }, + { + name: "should return an error if no expectation is satisfied - 1 wrong", + expectation: Any(expectation(false)), + wantErr: true, + }, + { + name: "should return an error if no expectation is satisfied - 2 wrong", + expectation: Any(expectation(false), expectation(false)), + wantErr: true, + }, + { + name: "should not return an error if any expectation is satisfied - 1 right, 1 wrong", + expectation: Any(expectation(false), expectation(true)), + }, + } + testBasicConditions(t, tests) +} + +func TestCombo(t *testing.T) { + tests := []basicConditionTest{ + { + name: "combo - nested", + expectation: Any( + Any(expectation(false)), //false + Any( + expectation(false), // false + expectation(false), // false + ), // false + All( + expectation(true), // true + Any( + expectation(false), // false + expectation(true), // true + ), // true + ), // true + ), // true + }, + { + name: "should return error for combo", + expectation: Any( + Any(expectation(false)), //false + All( + expectation(false), // false + expectation(true), // true + ), // false + Any( + expectation(false), // false + Any( + expectation(false), // false + ), // false + ), // false + ), // false + wantErr: true, + }, + } + testBasicConditions(t, tests) +} + +func TestAllWithCache(t *testing.T) { + passingExpectation := expectation(true) + failingExpectation := expectation(false) + c := All( + passingExpectation, + failingExpectation, + ).WithCaching() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer cancel() + + require.Equal(t, 0, passingExpectation.callexpectations["Verify"]) + require.Equal(t, 0, failingExpectation.callexpectations["Verify"]) + require.Error(t, c.Verify(ctx)) + + require.Equal(t, 1, passingExpectation.callexpectations["Verify"]) + require.Equal(t, 1, failingExpectation.callexpectations["Verify"]) + + require.Error(t, c.Verify(ctx)) + + require.Equal(t, 1, passingExpectation.callexpectations["Verify"]) + require.Equal(t, 2, failingExpectation.callexpectations["Verify"]) +} + +func testBasicConditions(t *testing.T, tt []basicConditionTest) { + for _, tt := range tt { + t.Run(tt.name, func(t *testing.T) { + require.NotEmpty(t, tt.expectation.Id()) + require.NotEmpty(t, tt.expectation.Description()) + + err := tt.expectation.Verify(context.Background()) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +type mockExpectation struct { + id string + callexpectations map[string]int + shouldPass bool +} + +// Description implements Expectation. +func (m mockExpectation) Description() string { + defer m.call("Description") + return "testing expectation" +} + +// Id implements Expectation. +func (m mockExpectation) Id() string { + defer m.call("Id") + return m.id +} + +// Verify implements Expectation. +func (m mockExpectation) Verify(ctx context.Context) error { + defer m.call("Verify") + if !m.shouldPass { + return errors.New("mock expectation failed") + } + return nil +} + +func (m mockExpectation) call(n string) { + m.callexpectations[n]++ +} + +func expectation(shouldPass bool) mockExpectation { + return mockExpectation{ + id: uuid.NewString(), + shouldPass: shouldPass, + callexpectations: make(map[string]int), + } +} diff --git a/packages/testutils/expectations/helm/README.md b/packages/testutils/expectations/helm/README.md new file mode 100644 index 000000000..80ba90333 --- /dev/null +++ b/packages/testutils/expectations/helm/README.md @@ -0,0 +1,219 @@ + + +# helm + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils/expectations/helm" +``` + +## Index + +- [Variables](<#variables>) +- [type HelmExpectation](<#HelmExpectation>) + - [func MustNew\(name, namespace string, opts ...Option\) \*HelmExpectation](<#MustNew>) + - [func MustNewAbsent\(name, namespace string, opts ...Option\) \*HelmExpectation](<#MustNewAbsent>) + - [func NewExpectation\(pattern, namespace string, opts ...Option\) \(\*HelmExpectation, error\)](<#NewExpectation>) + - [func \(e \*HelmExpectation\) AsGomegaSubject\(\) func\(context.Context\) \(interface\{\}, error\)](<#HelmExpectation.AsGomegaSubject>) + - [func \(he \*HelmExpectation\) Description\(\) string](<#HelmExpectation.Description>) + - [func \(he \*HelmExpectation\) Id\(\) string](<#HelmExpectation.Id>) + - [func \(e \*HelmExpectation\) ToGomegaMatcher\(\) gomega.GomegaMatcher](<#HelmExpectation.ToGomegaMatcher>) + - [func \(he \*HelmExpectation\) Verify\(c context.Context\) error](<#HelmExpectation.Verify>) +- [type ListRunner](<#ListRunner>) +- [type Option](<#Option>) + - [func WithDescription\(description string\) Option](<#WithDescription>) + - [func WithListClientBuilder\(builder func\(\) \(ListRunner, error\)\) Option](<#WithListClientBuilder>) + - [func WithLogger\(logger func\(format string, args ...interface\{\}\)\) Option](<#WithLogger>) + - [func WithReleaseCondition\(condition types.Condition\) Option](<#WithReleaseCondition>) + - [func WithReleaseListCondition\(condition types.Condition\) Option](<#WithReleaseListCondition>) + - [func WithRemoved\(removed bool\) Option](<#WithRemoved>) + - [func WithValueCondition\(condition types.Condition\) Option](<#WithValueCondition>) + - [func WithValueListCondition\(condition types.Condition\) Option](<#WithValueListCondition>) + + +## Variables + + + +```go +var ( + DeployedCondition = jq.Equality(".info.status", "deployed") + FailedCondition = jq.Equality(".info.status", "failed") +) +``` + + +## type [HelmExpectation]() + + + +```go +type HelmExpectation struct { + // contains filtered or unexported fields +} +``` + + +### func [MustNew]() + +```go +func MustNew(name, namespace string, opts ...Option) *HelmExpectation +``` + +MustNew creates a new helm expectation. It panics if the expectation cannot be created. + + +### func [MustNewAbsent]() + +```go +func MustNewAbsent(name, namespace string, opts ...Option) *HelmExpectation +``` + +NewPresent creates a new helm expectation that expects the release to be present. + + +### func [NewExpectation]() + +```go +func NewExpectation(pattern, namespace string, opts ...Option) (*HelmExpectation, error) +``` + +NewExpectation creates a new helm expectation. + + +### func \(\*HelmExpectation\) [AsGomegaSubject]() + +```go +func (e *HelmExpectation) AsGomegaSubject() func(context.Context) (interface{}, error) +``` + + + + +### func \(\*HelmExpectation\) [Description]() + +```go +func (he *HelmExpectation) Description() string +``` + + + + +### func \(\*HelmExpectation\) [Id]() + +```go +func (he *HelmExpectation) Id() string +``` + +Id implements types.Expectation. + + +### func \(\*HelmExpectation\) [ToGomegaMatcher]() + +```go +func (e *HelmExpectation) ToGomegaMatcher() gomega.GomegaMatcher +``` + + + + +### func \(\*HelmExpectation\) [Verify]() + +```go +func (he *HelmExpectation) Verify(c context.Context) error +``` + +Verify implements types.Expectation. + + +## type [ListRunner]() + + + +```go +type ListRunner interface { + Run() ([]*release.Release, error) +} +``` + + +## type [Option]() + + + +```go +type Option func(*HelmExpectation) +``` + + +### func [WithDescription]() + +```go +func WithDescription(description string) Option +``` + + + + +### func [WithListClientBuilder]() + +```go +func WithListClientBuilder(builder func() (ListRunner, error)) Option +``` + + + + +### func [WithLogger]() + +```go +func WithLogger(logger func(format string, args ...interface{})) Option +``` + + + + +### func [WithReleaseCondition]() + +```go +func WithReleaseCondition(condition types.Condition) Option +``` + + + + +### func [WithReleaseListCondition]() + +```go +func WithReleaseListCondition(condition types.Condition) Option +``` + + + + +### func [WithRemoved]() + +```go +func WithRemoved(removed bool) Option +``` + +WithRemoved specifies whether the release is expected to be present or not. + + +### func [WithValueCondition]() + +```go +func WithValueCondition(condition types.Condition) Option +``` + + + + +### func [WithValueListCondition]() + +```go +func WithValueListCondition(condition types.Condition) Option +``` + + + +Generated by [gomarkdoc]() diff --git a/packages/testutils/expectations/helm/commons.go b/packages/testutils/expectations/helm/commons.go new file mode 100644 index 000000000..42ef089d1 --- /dev/null +++ b/packages/testutils/expectations/helm/commons.go @@ -0,0 +1,8 @@ +package helm + +import "github.com/eclipse-symphony/symphony/packages/testutils/conditions/jq" + +var ( + DeployedCondition = jq.Equality(".info.status", "deployed") + FailedCondition = jq.Equality(".info.status", "failed") +) diff --git a/packages/testutils/expectations/helm/gomega.go b/packages/testutils/expectations/helm/gomega.go new file mode 100644 index 000000000..249337ddd --- /dev/null +++ b/packages/testutils/expectations/helm/gomega.go @@ -0,0 +1,32 @@ +package helm + +import ( + "context" + "fmt" + + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/onsi/gomega/gcustom" + gomega "github.com/onsi/gomega/types" + "helm.sh/helm/v3/pkg/release" +) + +var _ types.GomegaEventuallySubject = &HelmExpectation{} + +func (e *HelmExpectation) AsGomegaSubject() func(context.Context) (interface{}, error) { + return func(c context.Context) (interface{}, error) { + return e.getResults(c) + } +} + +func (e *HelmExpectation) ToGomegaMatcher() gomega.GomegaMatcher { + return gcustom.MakeMatcher(func(resource interface{}) (bool, error) { + releases, ok := resource.([]*release.Release) + if !ok { + return false, fmt.Errorf("expected resource to be a list of release.Release, got %T", resource) + } + if err := e.verifyConditions(context.TODO(), releases); err != nil { + return false, nil + } + return true, nil + }) +} diff --git a/packages/testutils/expectations/helm/options.go b/packages/testutils/expectations/helm/options.go new file mode 100644 index 000000000..c9ecfc859 --- /dev/null +++ b/packages/testutils/expectations/helm/options.go @@ -0,0 +1,53 @@ +package helm + +import "github.com/eclipse-symphony/symphony/packages/testutils/types" + +// WithRemoved specifies whether the release is expected to be present or not. +func WithRemoved(removed bool) Option { + return func(h *HelmExpectation) { + h.removed = removed + } +} + +func WithListClientBuilder(builder func() (ListRunner, error)) Option { + return func(h *HelmExpectation) { + h.actionBuilder = builder + } +} + +func WithValueListCondition(condition types.Condition) Option { + return func(h *HelmExpectation) { + newC := createValueConditionFrom(condition, true) + addCondition(&h.releaseListCondition, newC) + } +} +func WithValueCondition(condition types.Condition) Option { + return func(h *HelmExpectation) { + newC := createValueConditionFrom(condition, false) + addCondition(&h.releaseCondition, newC) + } +} + +func WithReleaseCondition(condition types.Condition) Option { + return func(h *HelmExpectation) { + addCondition(&h.releaseCondition, condition) + } +} + +func WithReleaseListCondition(condition types.Condition) Option { + return func(h *HelmExpectation) { + addCondition(&h.releaseListCondition, condition) + } +} + +func WithDescription(description string) Option { + return func(h *HelmExpectation) { + h.description = description + } +} + +func WithLogger(logger func(format string, args ...interface{})) Option { + return func(h *HelmExpectation) { + h.l = logger + } +} diff --git a/packages/testutils/expectations/helm/resource.go b/packages/testutils/expectations/helm/resource.go new file mode 100644 index 000000000..716e2ece6 --- /dev/null +++ b/packages/testutils/expectations/helm/resource.go @@ -0,0 +1,311 @@ +package helm + +import ( + "context" + "encoding/json" + "fmt" + "os" + "regexp" + "strings" + "time" + + "github.com/eclipse-symphony/symphony/packages/testutils/conditions" + "github.com/eclipse-symphony/symphony/packages/testutils/helpers" + ectx "github.com/eclipse-symphony/symphony/packages/testutils/internal/context" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/google/uuid" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/release" +) + +type ( + ListRunner interface { + Run() ([]*release.Release, error) + } + HelmExpectation struct { + // pattern is the pattern of the release + pattern string + + // description is a friendly description of the expectation + description string + + // removed indicates whether the release is expected to be present or not + removed bool + + // namespace is the namespace of the release + namespace string + + // releaseCondition specifies the types.that the release should satisfy + releaseCondition types.Condition + releaseListCondition types.Condition + + action ListRunner + actionBuilder func() (ListRunner, error) + l func(format string, args ...interface{}) + tick time.Duration + timeout time.Duration + nameRegex *regexp.Regexp + level int + id string + initialised bool + } + + Option func(*HelmExpectation) +) + +const ( + defaultTick = 5 * time.Second + defaultTimeout = 5 * time.Minute +) + +var _ types.Expectation = &HelmExpectation{} + +// NewExpectation creates a new helm expectation. +func NewExpectation(pattern, namespace string, opts ...Option) (*HelmExpectation, error) { + if namespace == "" { + return nil, fmt.Errorf("namespace cannot be empty") + } + + he := &HelmExpectation{ + pattern: boundPattern(pattern), + namespace: namespace, + tick: defaultTick, + timeout: defaultTimeout, + id: uuid.NewString(), + } + he.actionBuilder = getDefaultActionBuilder(namespace, he.log) + + for _, opt := range opts { + opt(he) + } + + nameRegex, err := regexp.Compile(he.pattern) + if err != nil { + return nil, err + } + he.nameRegex = nameRegex + + he.initializeCountCondition() + + return he, nil +} + +// MustNew creates a new helm expectation. It panics if the expectation cannot be created. +func MustNew(name, namespace string, opts ...Option) *HelmExpectation { + he, err := NewExpectation(name, namespace, opts...) + if err != nil { + panic(err) + } + return he +} + +// NewPresent creates a new helm expectation that expects the release to be present. +func MustNewAbsent(name, namespace string, opts ...Option) *HelmExpectation { + return MustNew(name, namespace, append(opts, WithRemoved(true))...) +} + +func (he *HelmExpectation) initAction() error { + if he.initialised { + return nil + } + action, err := he.actionBuilder() + if err != nil { + return err + } + he.action = action + he.initialised = true + return nil +} + +func (he *HelmExpectation) log(format string, args ...interface{}) { + s := fmt.Sprintf(format, args...) + format = "%s[%s]: %s\n" + args = []interface{}{strings.Repeat(" ", he.level), he.Description(), s} + + if he.l != nil { + he.l(format, args...) + } else { + logger.GetDefaultLogger()(format, args...) + } +} + +// Verify implements types.Expectation. +func (he *HelmExpectation) Verify(c context.Context) error { + ctx := ectx.From(c) + he.level = ctx.Level() + + return helpers.Eventually(ctx, func(ctx context.Context) (err error) { + he.log(strings.Repeat("-", 80)) + he.log("Verifying helm release") + defer func() { + if err != nil { + he.log("Error while verifying helm release: %s", err) + } + }() + + matches, err := he.getResults(ctx) + if err != nil { + return + } + if err = he.verifyConditions(ctx, matches); err != nil { + return + } + return nil + }, he.tick, "Timed out while verifying helm release %s", he.Description()) +} + +// Id implements types.Expectation. +func (he *HelmExpectation) Id() string { + return he.id +} + +func (re *HelmExpectation) initializeCountCondition() { + countCondition := conditions.GreaterThan(0) + if re.removed { + countCondition = conditions.Count(0) + } + + addCondition(&re.releaseListCondition, countCondition) +} + +func (he *HelmExpectation) getResults(ctx context.Context) ([]*release.Release, error) { + if err := he.initAction(); err != nil { + return nil, err + } + releases, err := he.action.Run() + if err != nil { + return nil, err + } + he.log("Found %d releases", len(releases)) + he.log("Action Type: %T", he.action) + matches := he.getMatches(releases) + he.log("Found %d matching releases", len(matches)) + return matches, nil +} + +func (he *HelmExpectation) verifyConditions(ctx context.Context, releases []*release.Release) error { + //Todo: Add support for list conditions + he.log("Verifying conditions") + arr := make([]map[string]interface{}, len(releases)) + b, err := json.Marshal(releases) + if err != nil { + return err + } + json.Unmarshal(b, &arr) + + if err := he.verifyListCondition(ctx, arr); err != nil { + return err + } + + if err := he.verifyUnitCondition(ctx, arr); err != nil { + return err + } + + return nil +} + +func (he *HelmExpectation) verifyUnitCondition(c context.Context, releases []map[string]interface{}) error { + ctx := ectx.From(c) + if he.releaseCondition != nil { + for _, release := range releases { + if err := he.releaseCondition.IsSatisfiedBy(ctx.Nested(), release); err != nil { + return err + } + } + } + return nil +} + +func (he *HelmExpectation) verifyListCondition(c context.Context, releases []map[string]interface{}) error { + ctx := ectx.From(c) + if he.releaseListCondition != nil { + return he.releaseListCondition.IsSatisfiedBy(ctx.Nested(), releases) + } + return nil +} + +func (he *HelmExpectation) getMatches(releases []*release.Release) (matches []*release.Release) { + for i := range releases { + if he.nameRegex.MatchString(releases[i].Name) { + matches = append(matches, releases[i]) + } + } + return matches +} + +func (he *HelmExpectation) Description() string { + if he.description != "" { + return he.description + } + return fmt.Sprintf("helm expectation: %s", he.pattern) +} + +func getDefaultActionBuilder(namespace string, logger logger.Logger) func() (ListRunner, error) { + return func() (ListRunner, error) { + settings := cli.New() + var allNamespaces bool + + if namespace == "*" { + allNamespaces = true + } else { + settings.SetNamespace(namespace) + } + + actionConfig := new(action.Configuration) + actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), logger) + action := action.NewList(actionConfig) + + action.AllNamespaces = allNamespaces + return action, nil + } +} + +func boundPattern(str string) string { + if !strings.HasPrefix(str, "^") { + str = "^" + str + } + if !strings.HasSuffix(str, "$") { + str = str + "$" + } + return str +} + +func createValueConditionFrom(condition types.Condition, isListCondition bool) types.Condition { + return conditions.Basic(func(ctx context.Context, i interface{}) error { + if isListCondition { + switch releases := i.(type) { + case []map[string]any: + values := make([]map[string]interface{}, len(releases)) + for i, release := range releases { + val, _ := release["config"].(map[string]interface{}) + values[i] = val + } + return condition.IsSatisfiedBy(ctx, values) + default: + return fmt.Errorf("expected []interface{}, got %T", i) + } + } + switch release := i.(type) { + case map[string]interface{}: + value, _ := release["config"].(map[string]interface{}) + return condition.IsSatisfiedBy(ctx, value) + + default: + return fmt.Errorf("expected map[string]interface{}, got %T", i) + } + }, conditions.WithBasicDescription("values check")) +} + +func addCondition(existingCondition *types.Condition, newCondition types.Condition) { + if *existingCondition != nil { + if c, ok := (*existingCondition).(interface { + And(...types.Condition) types.Condition + }); ok { + *existingCondition = c.And(newCondition) + } + } else { + *existingCondition = conditions.All(newCondition) + } +} diff --git a/packages/testutils/expectations/helm/resource_test.go b/packages/testutils/expectations/helm/resource_test.go new file mode 100644 index 000000000..ab4046e84 --- /dev/null +++ b/packages/testutils/expectations/helm/resource_test.go @@ -0,0 +1,309 @@ +package helm + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/eclipse-symphony/symphony/packages/testutils/conditions" + "github.com/eclipse-symphony/symphony/packages/testutils/conditions/jq" + "github.com/eclipse-symphony/symphony/packages/testutils/internal" + "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/release" +) + +type ( + mockLister struct { + count int + shoudError bool + } +) + +var ( + _ ListRunner = mockLister{} + testTimeout = time.Millisecond * 5 +) + +func TestNoErrorForNewResourceValidator(t *testing.T) { + e, err := NewExpectation("name", "*", + WithListClientBuilder(getMockListerBuilder(1, false)), + WithDescription("description"), + ) + + require.NoError(t, err) + require.NotEmpty(t, e.Description()) + require.NotEmpty(t, e.Id()) + +} + +func TestErrorForNewResourceWithIncorrectConfig(t *testing.T) { + _, err := NewExpectation("", "") // no pattern or namespace + require.Error(t, err) +} + +func TestErrorForNewResourceWithInvalidNameValidator(t *testing.T) { + _, err := NewExpectation("( ", "namespaace") + + require.Error(t, err) +} + +func TestShouldPanicForInvalidName(t *testing.T) { + require.Panics(t, func() { + MustNew("( ", "namespaace") + }) +} + +func TestShouldErrorWhenListFails(t *testing.T) { + e, err := NewExpectation("name", "namespace", WithListClientBuilder(getMockListerBuilder(0, true))) + + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + err = e.Verify(ctx) + + require.Error(t, err) +} + +func TestShouldFindMatchingReleaseFromExactName(t *testing.T) { + e, err := NewExpectation("release-0", "namespace", + WithListClientBuilder(getMockListerBuilder(1, false)), + WithReleaseCondition(jq.MustNew(`.chart.metadata.name`, jq.WithValue("chart-0"))), + ) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} + +func TestShouldFailOnMatchButFalseReleaseCondition(t *testing.T) { + e, err := NewExpectation("release-0", "namespace", + WithLogger(func(format string, args ...interface{}) {}), + WithListClientBuilder(getMockListerBuilder(1, false)), + WithReleaseCondition(jq.MustNew(`.chart.metadata.name`, jq.WithValue("wrong"))), + ) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} + +func TestShouldFailOnMatchButFalseValueCondition(t *testing.T) { + e, err := NewExpectation("release-0", "namespace", + WithListClientBuilder(getMockListerBuilder(1, false)), + WithValueCondition(jq.MustNew(`.foo`, jq.WithValue("baz"))), + ) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} + +func TestShouldFindMatchingReleaseFromExactNameAndExtraConditions(t *testing.T) { + e, err := NewExpectation("release-0", "namespace", + WithListClientBuilder(getMockListerBuilder(1, false)), + WithReleaseCondition(conditions.All( + jq.Equality(`.chart.metadata.name`, "chart-0"), + jq.Equality(`.chart.metadata.version`, "x.y.z"), + )), + WithReleaseListCondition(conditions.Count(1)), + WithValueCondition(conditions.All( + jq.Equality(`.foo`, "bar"), + jq.Equality(`.baz`, "qux"), + )), + ) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} + +func TestShouldNotFindMatchingReleaseWithUnexactName(t *testing.T) { + e, err := NewExpectation("release", "namespace", // No release with name "release" + WithListClientBuilder(getMockListerBuilder(1, false)), + ) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} +func TestShouldMatchAbsentRelease(t *testing.T) { + e := MustNewAbsent("some-non-existent-release", "*", // No release with name "some-non-existent-release" in any namespace + WithListClientBuilder(getMockListerBuilder(1, false)), + ) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} + +func TestShouldFindMultipleReleases(t *testing.T) { + e, err := NewExpectation("release-.+", "namespace", // regex to match all releases in namespace + WithListClientBuilder(getMockListerBuilder(5, false)), + WithReleaseListCondition( + conditions.Count(5), + ), + WithValueListCondition( + conditions.Count(5), + ), + ) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} + +func TestShouldFindMultipleReleasesButFailForIncorrectReleaseCondtion(t *testing.T) { + e, err := NewExpectation("release-.+", "namespace", // regex to match all releases in namespace + WithListClientBuilder(getMockListerBuilder(10, false)), + WithReleaseListCondition( + conditions.Count(5), // correct count is 10 + + ), + ) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} + +func TestShouldFindMultipleReleasesButFailForIncorrectValueCondtion(t *testing.T) { + e, err := NewExpectation("release-.+", "namespace", // regex to match all releases in namespace + WithListClientBuilder(getMockListerBuilder(10, false)), + WithValueListCondition( + conditions.Count(5), // correct count is 10 + ), + ) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} + +func TestVerifyPasses(t *testing.T) { + e := MustNew("release-0", "namespace", + WithListClientBuilder(getMockListerBuilder(1, false)), + WithReleaseCondition(jq.MustNew(`.chart.metadata.name`, jq.WithValue("chart-0"))), + ) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + + require.NoError(t, e.Verify(ctx)) +} + +func TestVerifyFailsWhenContextEnds(t *testing.T) { + e := MustNew("release-0", "namespace", + WithListClientBuilder(getMockListerBuilder(1, false)), + WithReleaseCondition(jq.MustNew(`.chart.metadata.name`, jq.WithValue("wrong"))), + ) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + + require.Error(t, e.Verify(ctx)) +} + +func TestWhenListBuilderInitializeFails(t *testing.T) { + ex, err := NewExpectation("release-0", "namespace", + WithListClientBuilder(func() (ListRunner, error) { + return nil, fmt.Errorf("some error") + }), + WithReleaseCondition(jq.MustNew(`.chart.metadata.name`, jq.WithValue("wrong"))), + ) + + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + + require.Error(t, ex.Verify(ctx)) +} + +func TestWorksWithGomegaAssertion(t *testing.T) { + mt := internal.NewMockT() + g := gomega.NewGomegaWithT(mt) + e, err := NewExpectation("release-0", "namespace", + WithListClientBuilder(getMockListerBuilder(1, false)), + WithReleaseCondition(conditions.All( + jq.Equality(`.chart.metadata.name`, "chart-0"), + jq.Equality(`.chart.metadata.version`, "x.y.z"), + )), // false runs the condiition on individual resources + WithReleaseListCondition(conditions.Count(1)), + WithValueCondition(conditions.All( + jq.Equality(`.foo`, "bar"), + jq.Equality(`.baz`, "qux"), + )), + ) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + mt.On("Helper").Return() + g.Eventually(e.AsGomegaSubject()).WithContext(ctx).Should(e.ToGomegaMatcher()) + mt.AssertExpectations(t) + + mt.On("Fatalf", mock.Anything, mock.Anything).Return() + g.Eventually(e.AsGomegaSubject()).WithContext(ctx).ShouldNot(e.ToGomegaMatcher()) + mt.AssertExpectations(t) +} + +func TestWorksWithGomegaAssertionFailingCondition(t *testing.T) { + mt := internal.NewMockT() + g := gomega.NewGomegaWithT(mt) + e, err := NewExpectation("release-0", "namespace", + WithListClientBuilder(getMockListerBuilder(1, false)), + WithReleaseCondition(conditions.All( + jq.Equality(`.chart.metadata.name`, "chart-0"), + jq.Equality(`.chart.metadata.version`, "x.y.z"), + )), + WithReleaseListCondition(conditions.Count(1)), + WithValueCondition(conditions.All( + jq.Equality(`.foo`, "wrong"), // wrong value + jq.Equality(`.baz`, "qux"), + )), + ) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + mt.On("Helper").Return() + g.Eventually(e.AsGomegaSubject()).WithContext(ctx).ShouldNot(e.ToGomegaMatcher()) + mt.AssertExpectations(t) + + mt.On("Fatalf", mock.Anything, mock.Anything).Return() + g.Eventually(e.AsGomegaSubject()).WithContext(ctx).Should(e.ToGomegaMatcher()) + mt.AssertExpectations(t) +} + +func (ml mockLister) Run() ([]*release.Release, error) { + if ml.shoudError { + return nil, fmt.Errorf("some error") + } + releases := make([]*release.Release, ml.count) + for i := 0; i < ml.count; i++ { + releases[i] = &release.Release{ + Name: fmt.Sprintf("release-%d", i), + Config: map[string]interface{}{ + "foo": "bar", + "baz": "qux", + }, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: fmt.Sprintf("chart-%d", i), + Version: "x.y.z", + }, + }, + } + } + return releases, nil +} + +func getMockListerBuilder(count int, shouldError bool) func() (ListRunner, error) { + return func() (ListRunner, error) { + return mockLister{ + count: count, + shoudError: shouldError, + }, nil + } +} diff --git a/packages/testutils/expectations/kube/README.md b/packages/testutils/expectations/kube/README.md new file mode 100644 index 000000000..36777ab9c --- /dev/null +++ b/packages/testutils/expectations/kube/README.md @@ -0,0 +1,351 @@ + + +# kube + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils/expectations/kube" +``` + +## Index + +- [Variables](<#variables>) +- [func NewAnnotationMatchCondition\(annotation string, value string\) types.Condition](<#NewAnnotationMatchCondition>) +- [func NewKubernetesStatusCondition\(conditionType string, status bool\) types.Condition](<#NewKubernetesStatusCondition>) +- [func NewLabelMatchCondition\(label string, value string\) types.Condition](<#NewLabelMatchCondition>) +- [func ProvisioningStatusComponentOutput\(componentKey string, value interface\{\}\) types.Condition](<#ProvisioningStatusComponentOutput>) +- [type KubeExpectation](<#KubeExpectation>) + - [func AbsentInstance\(name, namespace string, opts ...Option\) \(\*KubeExpectation, error\)](<#AbsentInstance>) + - [func AbsentPod\(name, namespace string, opts ...Option\) \(\*KubeExpectation, error\)](<#AbsentPod>) + - [func AbsentResource\(name, namespace string, gvk schema.GroupVersionKind, opts ...Option\) \(\*KubeExpectation, error\)](<#AbsentResource>) + - [func AbsentSolution\(name, namespace string, opts ...Option\) \(\*KubeExpectation, error\)](<#AbsentSolution>) + - [func AbsentTarget\(name, namespace string, opts ...Option\) \(\*KubeExpectation, error\)](<#AbsentTarget>) + - [func Instance\(name, namespace string, opts ...Option\) \(\*KubeExpectation, error\)](<#Instance>) + - [func Must\(resource \*KubeExpectation, err error\) \*KubeExpectation](<#Must>) + - [func Pod\(name, namespace string, opts ...Option\) \(\*KubeExpectation, error\)](<#Pod>) + - [func Resource\(pattern, namespace string, gvk schema.GroupVersionKind, opts ...Option\) \(\*KubeExpectation, error\)](<#Resource>) + - [func Solution\(name, namespace string, opts ...Option\) \(\*KubeExpectation, error\)](<#Solution>) + - [func Target\(name, namespace string, opts ...Option\) \(\*KubeExpectation, error\)](<#Target>) + - [func \(e \*KubeExpectation\) AsGomegaSubject\(\) func\(context.Context\) \(interface\{\}, error\)](<#KubeExpectation.AsGomegaSubject>) + - [func \(re \*KubeExpectation\) Description\(\) string](<#KubeExpectation.Description>) + - [func \(re \*KubeExpectation\) Id\(\) string](<#KubeExpectation.Id>) + - [func \(e \*KubeExpectation\) ToGomegaMatcher\(\) gomega.GomegaMatcher](<#KubeExpectation.ToGomegaMatcher>) + - [func \(re \*KubeExpectation\) Verify\(c context.Context\) error](<#KubeExpectation.Verify>) +- [type Option](<#Option>) + - [func IsAbsent\(\) Option](<#IsAbsent>) + - [func WithCondition\(condition types.Condition\) Option](<#WithCondition>) + - [func WithDescription\(description string\) Option](<#WithDescription>) + - [func WithDiscoveryClientBuilder\(builder func\(\) \(discovery.DiscoveryInterface, error\)\) Option](<#WithDiscoveryClientBuilder>) + - [func WithDynamicClientBuilder\(builder func\(\) \(dynamic.Interface, error\)\) Option](<#WithDynamicClientBuilder>) + - [func WithListCondition\(condition types.Condition\) Option](<#WithListCondition>) + - [func WithLogger\(logger func\(format string, args ...interface\{\}\)\) Option](<#WithLogger>) + - [func WithTick\(tick time.Duration\) Option](<#WithTick>) + + +## Variables + + + +```go +var ( + PodReadyCondition types.Condition = conditions.All( + NewKubernetesStatusCondition("Ready", true), + NewKubernetesStatusCondition("Initialized", true), + NewKubernetesStatusCondition("ContainersReady", true), + ) // can be used for pods and certificates + + DeploymentCompleteCondition types.Condition = conditions.All( + NewKubernetesStatusCondition("Available", true), + NewKubernetesStatusCondition("Progressing", true), + ) // can be used for deployments and statefulsets + + // AioManagerLabelCondition is a condition that checks if the resource is managed by the aio orc api + AioManagerLabelCondition types.Condition = NewLabelMatchCondition("iotoperations.azure.com/managed-by", "symphony-api") + + // ProvisioningSucceededCondition is a condition that checks if the resource has succeeded provisioning + ProvisioningSucceededCondition types.Condition = jq.Equality(".status.provisioningStatus.status", "Succeeded", statusDescription) + + // ProvisioningFailedCondition is a condition that checks if the resource has failed provisioning + ProvisioningFailedCondition types.Condition = jq.Equality(".status.provisioningStatus.status", "Failed", statusDescription) + // OperationIdMatchCondition is a condition that checks if the resource has the operation id annotation and + // ensures that it matches the operationId in the status of the resource + OperationIdMatchCondition types.Condition = jq.MustNew( + fmt.Sprintf(`.metadata.annotations["%s"]`, "management.azure.com/operationId"), + jq.WithCustomMatcher(operationJqMatcher), + jq.WithDescription("Operation Id"), + ) +) +``` + + +## func [NewAnnotationMatchCondition]() + +```go +func NewAnnotationMatchCondition(annotation string, value string) types.Condition +``` + +NewAnnotationMatchCondition returns a condition that checks if the resource has the annotation and value + + +## func [NewKubernetesStatusCondition]() + +```go +func NewKubernetesStatusCondition(conditionType string, status bool) types.Condition +``` + +NewKubernetesStatusCondition returns a condition that checks the status of a kubernetes resource's condition + + +## func [NewLabelMatchCondition]() + +```go +func NewLabelMatchCondition(label string, value string) types.Condition +``` + +NewLabelMatchCondition returns a condition that checks if the resource has the label and value + + +## func [ProvisioningStatusComponentOutput]() + +```go +func ProvisioningStatusComponentOutput(componentKey string, value interface{}) types.Condition +``` + + + + +## type [KubeExpectation]() + + + +```go +type KubeExpectation struct { + // contains filtered or unexported fields +} +``` + + +### func [AbsentInstance]() + +```go +func AbsentInstance(name, namespace string, opts ...Option) (*KubeExpectation, error) +``` + +AbsentInstance returns an expectation that the instance\(s\) is/are absent from the cluster + + +### func [AbsentPod]() + +```go +func AbsentPod(name, namespace string, opts ...Option) (*KubeExpectation, error) +``` + +AbsentPod returns an expectation that the pod\(s\) is/are absent from the cluster + + +### func [AbsentResource]() + +```go +func AbsentResource(name, namespace string, gvk schema.GroupVersionKind, opts ...Option) (*KubeExpectation, error) +``` + +AbsentResource returns an expectation for the resources is/are absent from the cluster + + +### func [AbsentSolution]() + +```go +func AbsentSolution(name, namespace string, opts ...Option) (*KubeExpectation, error) +``` + +AbsentSolution returns an expectation that the solution\(s\) is/are absent from the cluster + + +### func [AbsentTarget]() + +```go +func AbsentTarget(name, namespace string, opts ...Option) (*KubeExpectation, error) +``` + +AbsentTarget returns an expectation that the target\(s\) is/are absent from the cluster + + +### func [Instance]() + +```go +func Instance(name, namespace string, opts ...Option) (*KubeExpectation, error) +``` + +Instance returns an expectation for a instance\(s\) in the cluster + + +### func [Must]() + +```go +func Must(resource *KubeExpectation, err error) *KubeExpectation +``` + +Must returns a resource expectation or panics if there is an error + + +### func [Pod]() + +```go +func Pod(name, namespace string, opts ...Option) (*KubeExpectation, error) +``` + +Pod returns an expectation expectation for a pod\(s\) in the cluster + + +### func [Resource]() + +```go +func Resource(pattern, namespace string, gvk schema.GroupVersionKind, opts ...Option) (*KubeExpectation, error) +``` + + + + +### func [Solution]() + +```go +func Solution(name, namespace string, opts ...Option) (*KubeExpectation, error) +``` + +Solution returns an expectation for a solution\(s\) in the cluster + + +### func [Target]() + +```go +func Target(name, namespace string, opts ...Option) (*KubeExpectation, error) +``` + +Target returns an expectation for a target\(s\) in the cluster + + +### func \(\*KubeExpectation\) [AsGomegaSubject]() + +```go +func (e *KubeExpectation) AsGomegaSubject() func(context.Context) (interface{}, error) +``` + + + + +### func \(\*KubeExpectation\) [Description]() + +```go +func (re *KubeExpectation) Description() string +``` + + + + +### func \(\*KubeExpectation\) [Id]() + +```go +func (re *KubeExpectation) Id() string +``` + + + + +### func \(\*KubeExpectation\) [ToGomegaMatcher]() + +```go +func (e *KubeExpectation) ToGomegaMatcher() gomega.GomegaMatcher +``` + + + + +### func \(\*KubeExpectation\) [Verify]() + +```go +func (re *KubeExpectation) Verify(c context.Context) error +``` + +Verify implements types.Expectation. + + +## type [Option]() + + + +```go +type Option func(*KubeExpectation) +``` + + +### func [IsAbsent]() + +```go +func IsAbsent() Option +``` + + + + +### func [WithCondition]() + +```go +func WithCondition(condition types.Condition) Option +``` + +WithCondition specifies the conditions that the resource should satisfy. + + +### func [WithDescription]() + +```go +func WithDescription(description string) Option +``` + + + + +### func [WithDiscoveryClientBuilder]() + +```go +func WithDiscoveryClientBuilder(builder func() (discovery.DiscoveryInterface, error)) Option +``` + + + + +### func [WithDynamicClientBuilder]() + +```go +func WithDynamicClientBuilder(builder func() (dynamic.Interface, error)) Option +``` + + + + +### func [WithListCondition]() + +```go +func WithListCondition(condition types.Condition) Option +``` + +WithListCondition specifies the conditions that the list of matched resources should satisfy. + + +### func [WithLogger]() + +```go +func WithLogger(logger func(format string, args ...interface{})) Option +``` + +WithLogger specifies the logger to be used. + + +### func [WithTick]() + +```go +func WithTick(tick time.Duration) Option +``` + +WithTick specifies the tick for the expectation. + +Generated by [gomarkdoc]() diff --git a/packages/testutils/expectations/kube/commons.go b/packages/testutils/expectations/kube/commons.go new file mode 100644 index 000000000..ae40c0393 --- /dev/null +++ b/packages/testutils/expectations/kube/commons.go @@ -0,0 +1,177 @@ +package kube + +import ( + "context" + "fmt" + + "github.com/eclipse-symphony/symphony/packages/testutils/conditions" + "github.com/eclipse-symphony/symphony/packages/testutils/conditions/jq" + "github.com/eclipse-symphony/symphony/packages/testutils/conditions/jsonpath" + "github.com/eclipse-symphony/symphony/packages/testutils/helpers" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + statusDescription = jq.WithDescription("Provisioning Status") +) + +var ( + /** + * These are some common kubernetes resource conditions + */ + + PodReadyCondition types.Condition = conditions.All( + NewKubernetesStatusCondition("Ready", true), + NewKubernetesStatusCondition("Initialized", true), + NewKubernetesStatusCondition("ContainersReady", true), + ) // can be used for pods and certificates + + DeploymentCompleteCondition types.Condition = conditions.All( + NewKubernetesStatusCondition("Available", true), + NewKubernetesStatusCondition("Progressing", true), + ) // can be used for deployments and statefulsets + + /** + * These are some common conditions for azure iot orchestration resources + */ + + // AioManagerLabelCondition is a condition that checks if the resource is managed by the aio orc api + AioManagerLabelCondition types.Condition = NewLabelMatchCondition("iotoperations.azure.com/managed-by", "symphony-api") + + // ProvisioningSucceededCondition is a condition that checks if the resource has succeeded provisioning + ProvisioningSucceededCondition types.Condition = jq.Equality(".status.provisioningStatus.status", "Succeeded", statusDescription) + + // ProvisioningFailedCondition is a condition that checks if the resource has failed provisioning + ProvisioningFailedCondition types.Condition = jq.Equality(".status.provisioningStatus.status", "Failed", statusDescription) + // OperationIdMatchCondition is a condition that checks if the resource has the operation id annotation and + // ensures that it matches the operationId in the status of the resource + OperationIdMatchCondition types.Condition = jq.MustNew( + fmt.Sprintf(`.metadata.annotations["%s"]`, "management.azure.com/operationId"), + jq.WithCustomMatcher(operationJqMatcher), + jq.WithDescription("Operation Id"), + ) +) + +func operationJqMatcher(ctx context.Context, value, resource interface{}, log logger.Logger) error { + operationId, err := getProvisioningOperationIdFromStatus(resource.(map[string]interface{})) + if err != nil { + return err + } + switch value := value.(type) { + case string: + log("Comparing %s with %s", operationId, value) + if operationId == value { + return nil + } + return fmt.Errorf("expected %s, got %s", operationId, value) + default: + return fmt.Errorf("expected operationId to be string, got %T", value) + } +} + +func getProvisioningOperationIdFromStatus(resource map[string]interface{}) (string, error) { + status, ok := resource["status"].(map[string]interface{}) + if !ok { + return "", fmt.Errorf("status field not found") + } + provisioningStatus, ok := status["provisioningStatus"].(map[string]interface{}) + if !ok { + return "", fmt.Errorf("provisioningStatus field not found") + } + operationId, ok := provisioningStatus["operationId"].(string) + if !ok { + return "", fmt.Errorf("operationId field not found") + } + return operationId, nil +} + +// NewKubernetesStatusCondition returns a condition that checks the status of a kubernetes resource's condition +func NewKubernetesStatusCondition(conditionType string, status bool) types.Condition { + statusString := "False" + if status { + statusString = "True" + } + return jsonpath.MustNew( + fmt.Sprintf("$.status.conditions[?(@.type == '%s')].status", conditionType), + jsonpath.WithValue([]interface{}{statusString}), + jsonpath.WithDescription(fmt.Sprintf("Condition %s", conditionType)), + ) +} + +// NewLabelMatchCondition returns a condition that checks if the resource has the label and value +func NewLabelMatchCondition(label string, value string) types.Condition { + return jq.Equality( + fmt.Sprintf(`.metadata.labels["%s"]`, label), + jq.WithValue(value), + jq.WithDescription(fmt.Sprintf("Label %s", label)), + ) +} + +func ProvisioningStatusComponentOutput(componentKey string, value interface{}) types.Condition { + return jq.Equality(fmt.Sprintf(`.status.provisioningStatus.output["%s"]`, componentKey), value) +} + +// AbsentResource returns an expectation for the resources is/are absent from the cluster +func AbsentResource(name, namespace string, gvk schema.GroupVersionKind, opts ...Option) (*KubeExpectation, error) { + opts = append(opts, IsAbsent()) + return Resource(name, namespace, gvk, opts...) +} + +// NewAnnotationMatchCondition returns a condition that checks if the resource has the annotation and value +func NewAnnotationMatchCondition(annotation string, value string) types.Condition { + return jq.MustNew( + fmt.Sprintf(`.metadata.annotations["%s"]`, annotation), + jq.WithValue(value), + jq.WithDescription(fmt.Sprintf("Annotation %s", annotation)), + ) +} + +// Pod returns an expectation expectation for a pod(s) in the cluster +func Pod(name, namespace string, opts ...Option) (*KubeExpectation, error) { + return Resource(name, namespace, helpers.PodGVK, opts...) +} + +// AbsentPod returns an expectation that the pod(s) is/are absent from the cluster +func AbsentPod(name, namespace string, opts ...Option) (*KubeExpectation, error) { + return AbsentResource(name, namespace, helpers.PodGVK, opts...) +} + +// Target returns an expectation for a target(s) in the cluster +func Target(name, namespace string, opts ...Option) (*KubeExpectation, error) { + return Resource(name, namespace, helpers.TargetGVK, opts...) +} + +// AbsentTarget returns an expectation that the target(s) is/are absent from the cluster +func AbsentTarget(name, namespace string, opts ...Option) (*KubeExpectation, error) { + return AbsentResource(name, namespace, helpers.TargetGVK, opts...) +} + +// Instance returns an expectation for a instance(s) in the cluster +func Instance(name, namespace string, opts ...Option) (*KubeExpectation, error) { + return Resource(name, namespace, helpers.InstanceGVK, opts...) +} + +// AbsentInstance returns an expectation that the instance(s) is/are absent from the cluster +func AbsentInstance(name, namespace string, opts ...Option) (*KubeExpectation, error) { + return AbsentResource(name, namespace, helpers.InstanceGVK, opts...) +} + +// Solution returns an expectation for a solution(s) in the cluster +func Solution(name, namespace string, opts ...Option) (*KubeExpectation, error) { + return Resource(name, namespace, helpers.SolutionGVK, opts...) +} + +// AbsentSolution returns an expectation that the solution(s) is/are absent from the cluster +func AbsentSolution(name, namespace string, opts ...Option) (*KubeExpectation, error) { + return AbsentResource(name, namespace, helpers.SolutionGVK, opts...) +} + +// Must returns a resource expectation or panics if there is an error +func Must(resource *KubeExpectation, err error) *KubeExpectation { + if err != nil { + panic(err) + } + return resource +} diff --git a/packages/testutils/expectations/kube/gomega.go b/packages/testutils/expectations/kube/gomega.go new file mode 100644 index 000000000..96de220b9 --- /dev/null +++ b/packages/testutils/expectations/kube/gomega.go @@ -0,0 +1,34 @@ +package kube + +import ( + "context" + "fmt" + + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/onsi/gomega/gcustom" + gomega "github.com/onsi/gomega/types" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +var ( + _ types.GomegaEventuallySubject = &KubeExpectation{} +) + +func (e *KubeExpectation) AsGomegaSubject() func(context.Context) (interface{}, error) { + return func(c context.Context) (interface{}, error) { + return e.getResults(c) + } +} + +func (e *KubeExpectation) ToGomegaMatcher() gomega.GomegaMatcher { + return gcustom.MakeMatcher(func(resource interface{}) (bool, error) { + list, ok := resource.([]*unstructured.Unstructured) + if !ok { + return false, fmt.Errorf("expected resource to be a list of unstructured.Unstructured, got %T", resource) + } + if err := e.verifyConditions(context.TODO(), list); err != nil { + return false, nil + } + return true, nil + }) +} diff --git a/packages/testutils/expectations/kube/options.go b/packages/testutils/expectations/kube/options.go new file mode 100644 index 000000000..5448a2846 --- /dev/null +++ b/packages/testutils/expectations/kube/options.go @@ -0,0 +1,61 @@ +package kube + +import ( + "time" + + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" +) + +// WithCondition specifies the conditions that the resource should satisfy. +func WithCondition(condition types.Condition) Option { + return func(re *KubeExpectation) { + re.condition = condition + } +} + +// WithListCondition specifies the conditions that the list of matched resources should satisfy. +func WithListCondition(condition types.Condition) Option { + return func(re *KubeExpectation) { + re.listCondition = condition + } +} + +// WithLogger specifies the logger to be used. +func WithLogger(logger func(format string, args ...interface{})) Option { + return func(re *KubeExpectation) { + re.l = logger + } +} + +// WithTick specifies the tick for the expectation. +func WithTick(tick time.Duration) Option { + return func(re *KubeExpectation) { + re.tick = tick + } +} + +func WithDescription(description string) Option { + return func(h *KubeExpectation) { + h.description = description + } +} + +func WithDynamicClientBuilder(builder func() (dynamic.Interface, error)) Option { + return func(h *KubeExpectation) { + h.dynamicClientBuilder = builder + } +} + +func WithDiscoveryClientBuilder(builder func() (discovery.DiscoveryInterface, error)) Option { + return func(h *KubeExpectation) { + h.discoveryClientBuilder = builder + } +} + +func IsAbsent() Option { + return func(re *KubeExpectation) { + re.removed = true + } +} diff --git a/packages/testutils/expectations/kube/resource.go b/packages/testutils/expectations/kube/resource.go new file mode 100644 index 000000000..f8b61adad --- /dev/null +++ b/packages/testutils/expectations/kube/resource.go @@ -0,0 +1,284 @@ +package kube + +import ( + "context" + "fmt" + "regexp" + "strings" + "time" + + "github.com/eclipse-symphony/symphony/packages/testutils/conditions" + "github.com/eclipse-symphony/symphony/packages/testutils/helpers" + ectx "github.com/eclipse-symphony/symphony/packages/testutils/internal/context" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/google/uuid" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/memory" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/restmapper" +) + +type ( + KubeExpectation struct { + // pattern is the prefix of the resource pattern + pattern string + + // description is a friendly description of the expectation + description string + + // gvk is the group, version, kind of the resource + gvk schema.GroupVersionKind + + // namespace is the namespace of the resource if applicable + namespace string + + // Removed indicates whether the resource is expected to be present or not + removed bool + + // conditions specifies the condition that the resource should satisfy + condition types.Condition + + // listCondition specifies the condition that the list of resources should satisfy + listCondition types.Condition + + discoveryClient discovery.DiscoveryInterface + dynamicClient dynamic.Interface + mapper meta.RESTMapper + discoveryClientBuilder func() (discovery.DiscoveryInterface, error) + dynamicClientBuilder func() (dynamic.Interface, error) + + tick time.Duration + l func(format string, args ...interface{}) + nameRegex *regexp.Regexp + level int + id string + initialized bool + } + + Option func(*KubeExpectation) +) + +const ( + defaultTimeout = 10 * time.Minute + defaultTick = 10 * time.Second +) + +var ( + _ types.Expectation = &KubeExpectation{} +) + +func Resource(pattern, namespace string, gvk schema.GroupVersionKind, opts ...Option) (*KubeExpectation, error) { + re := KubeExpectation{ + pattern: boundPattern(pattern), + gvk: gvk, + tick: defaultTick, + discoveryClientBuilder: defaultDiscoveryClientBuilder, + dynamicClientBuilder: defaultDynamicClientBuilder, + namespace: namespace, + id: uuid.NewString(), + } + + compiled, err := regexp.Compile(boundPattern(pattern)) + if err != nil { + return nil, err + } + re.nameRegex = compiled + + for _, opt := range opts { + opt(&re) + } + + re.initializeCountCondition() + return &re, nil +} + +func (re *KubeExpectation) initClients() error { + if re.initialized { + return nil + } + discoveryClient, err := re.discoveryClientBuilder() + if err != nil { + return err + } + re.discoveryClient = discoveryClient + + dynamicClient, err := re.dynamicClientBuilder() + if err != nil { + return err + } + re.dynamicClient = dynamicClient + re.mapper = restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(re.discoveryClient)) + re.initialized = true + return nil +} + +func (re *KubeExpectation) initializeCountCondition() { + countCondition := conditions.GreaterThan(0) + if re.removed { + countCondition = conditions.Count(0) + } + if re.listCondition != nil { + re.listCondition = conditions.All(countCondition, re.listCondition) + } else { + re.listCondition = countCondition + } +} + +func (re *KubeExpectation) log(format string, args ...interface{}) { + s := fmt.Sprintf(format, args...) + format = "%s[%s]: %s\n" + args = []interface{}{strings.Repeat(" ", re.level), re.Description(), s} + + if re.l != nil { + re.l(format, args...) + } else { + logger.GetDefaultLogger()(format, args...) + } +} + +// Verify implements types.Expectation. +func (re *KubeExpectation) Verify(c context.Context) error { + ctx := ectx.From(c) + re.level = ctx.Level() + + return helpers.Eventually(ctx, func(ctx context.Context) error { + re.log(strings.Repeat("-", 80)) + re.log(`Verifying resource`) + err := re.verify(ctx, re.condition) + if err != nil { + re.log("Resource verification failed: %v", err) + return err + } + return nil + }, re.tick, "Timed out while verifying resource %s of kind: [%s]", re.pattern, re.gvk.String()) +} + +func (re *KubeExpectation) Description() string { + if re.description != "" { + return re.description + } + return fmt.Sprintf("%s: %s", re.gvk.String(), re.pattern) +} + +func (re *KubeExpectation) Id() string { + return re.id +} + +func (re *KubeExpectation) getResults(ctx context.Context) ([]*unstructured.Unstructured, error) { + if err := re.initClients(); err != nil { + return nil, err + } + var namespaced bool + + mapping, err := re.mapper.RESTMapping(re.gvk.GroupKind(), re.gvk.Version) + if err != nil { + return nil, err + } + namespaced = mapping.Scope.Name() == meta.RESTScopeNameNamespace + + if namespaced && re.namespace == "" { + return nil, fmt.Errorf("namespace is required for namespaced resources") + } + + var list *unstructured.UnstructuredList + + if namespaced { + namespace := re.namespace + if namespace == "*" { + namespace = metav1.NamespaceAll + } + list, err = re.dynamicClient.Resource(mapping.Resource).Namespace(namespace).List(ctx, metav1.ListOptions{}) + } else { + list, err = re.dynamicClient.Resource(mapping.Resource).List(ctx, metav1.ListOptions{}) + } + + if err != nil { + return nil, err + } + + return re.getMatches(list), nil +} + +func (re *KubeExpectation) verify(ctx context.Context, condition types.Condition) (err error) { + + matches, err := re.getResults(ctx) + if err != nil { + return err + } + re.log("Resource matches returned. %d matches", len(matches)) + + return re.verifyConditions(ctx, matches) +} + +func (re *KubeExpectation) verifyConditions(ctx context.Context, matches []*unstructured.Unstructured) (err error) { + err = re.evaluateListCondition(ctx, matches, re.listCondition) + if err != nil { + return + } + + err = re.evaluateCondition(ctx, matches, re.condition) + if err != nil { + return + } + + return nil +} + +func (re *KubeExpectation) evaluateCondition(c context.Context, objects []*unstructured.Unstructured, condition types.Condition) (err error) { + ctx := ectx.From(c) + if condition != nil { + for _, object := range objects { + err = condition.IsSatisfiedBy(ctx.Nested(), object.Object) + if err != nil { + return err + } + } + } + + return nil +} + +func (re *KubeExpectation) evaluateListCondition(c context.Context, objects []*unstructured.Unstructured, condition types.Condition) (err error) { + ctx := ectx.From(c) + if condition != nil { + err = condition.IsSatisfiedBy(ctx.Nested(), objects) + if err != nil { + return err + } + } + + return nil +} + +func (re *KubeExpectation) getMatches(list *unstructured.UnstructuredList) []*unstructured.Unstructured { + matches := make([]*unstructured.Unstructured, 0) + for i := range list.Items { + if re.nameRegex.MatchString(list.Items[i].GetName()) { + matches = append(matches, &list.Items[i]) + } + } + return matches +} + +func boundPattern(str string) string { + if !strings.HasPrefix(str, "^") { + str = "^" + str + } + if !strings.HasSuffix(str, "$") { + str = str + "$" + } + return str +} + +func defaultDiscoveryClientBuilder() (discovery.DiscoveryInterface, error) { + return helpers.DiscoveryClient() +} + +func defaultDynamicClientBuilder() (dynamic.Interface, error) { + return helpers.DynamicClient() +} diff --git a/packages/testutils/expectations/kube/resource_test.go b/packages/testutils/expectations/kube/resource_test.go new file mode 100644 index 000000000..86eebc749 --- /dev/null +++ b/packages/testutils/expectations/kube/resource_test.go @@ -0,0 +1,419 @@ +package kube + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/eclipse-symphony/symphony/packages/testutils/conditions" + "github.com/eclipse-symphony/symphony/packages/testutils/conditions/jq" + "github.com/eclipse-symphony/symphony/packages/testutils/helpers" + "github.com/eclipse-symphony/symphony/packages/testutils/internal" + gomega "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + fakeDiscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/dynamic" + fakeDynamic "k8s.io/client-go/dynamic/fake" +) + +var ( + testResources = []runtime.Object{ + internal.Pod("test-1", "namespace-1"), + internal.Pod("test-2", "namespace-2"), + internal.Pod("different-1", "namespace-1"), + internal.Pod("different-2", "namespace-2"), + internal.Resource("config-1", "namespace-1", helpers.GVK("", "v1", "ConfigMap")), + internal.Resource("config-2", "namespace-2", helpers.GVK("", "v1", "ConfigMap")), + internal.Target("test-1", "namespace-1"), + internal.OutOfSyncResource("test-2", "namespace-2", helpers.TargetGVK), + internal.Namespace("namespace-1"), + internal.Namespace("namespace-2"), + } + testScheme = getScheme() + testDynamicClient = fakeDynamic.NewSimpleDynamicClient(testScheme, testResources...) + testDiscovery = generateTestDiscoveryClient() + testTimeout = time.Millisecond * 5 +) + +func getScheme() *runtime.Scheme { + s := runtime.NewScheme() + s.AddKnownTypes( + schema.GroupVersion{Group: "", Version: "v1"}, + &corev1.Pod{}, + &corev1.PodList{}, + &corev1.Namespace{}, + &corev1.ConfigMap{}, + &corev1.ConfigMapList{}, + &corev1.NamespaceList{}, + ) + s.AddKnownTypes( + helpers.TargetGVK.GroupVersion(), + &unstructured.Unstructured{}, + &unstructured.UnstructuredList{}, + ) + return s +} + +func generateTestDiscoveryClient() *fakeDiscovery.FakeDiscovery { + f := &fakeDiscovery.FakeDiscovery{ + Fake: &testDynamicClient.Fake, + } + f.Resources = internal.GenerateTestApiResourceList() + return f +} + +func testDynamicClientBuilder() (dynamic.Interface, error) { + return testDynamicClient, nil +} + +func testDiscoveryClientBuilder() (discovery.DiscoveryInterface, error) { + return testDiscovery, nil +} + +func TestSuccess(t *testing.T) { + r, err := Resource("test", "*", helpers.GVK("", "v1", "Pod")) + require.NoError(t, err) + require.NotEmpty(t, r.Description()) + require.NotEmpty(t, r.Id()) +} + +func TestSuccessAlternateDescription(t *testing.T) { + r, err := Resource("test", "*", helpers.GVK("", "v1", "Pod"), WithDescription("alternate")) + require.NoError(t, err) + require.Equal(t, "alternate", r.Description()) +} + +func TestFailOnInvalidPattern(t *testing.T) { + _, err := Resource("test(", "namespace-1", helpers.GVK("", "v1", "Pod")) // invalid pattern + require.Error(t, err) +} + +func TestMustSucceed(t *testing.T) { + require.NotPanics(t, func() { + Must(Resource("test", "*", helpers.GVK("", "v1", "Pod"))) + }) +} + +func TestMustPanics(t *testing.T) { + require.Panics(t, func() { + Must(Resource("test (", "", helpers.GVK("", "v1", "Pod"))) // invalid pattern + }) +} + +func TestFindsSinglePodInNamespace(t *testing.T) { + e := Must(Resource("test-1", "namespace-1", helpers.GVK("", "v1", "Pod"), + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithCondition( + jq.MustNew(".spec.containers[0].name", jq.WithValue("test-1")), + ), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} + +func TestFindsSinglePodInNamespaceWithFailingCondition(t *testing.T) { + e := Must(Resource("test-1", "namespace-1", helpers.GVK("", "v1", "Pod"), + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithCondition( + jq.MustNew(".spec.containers[0].name", jq.WithValue("wrong")), + ), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} + +func TestExpectedCountSuccess(t *testing.T) { + e := Must(Resource("test.+", "*", helpers.GVK("", "v1", "Pod"), // finds all pods starting with "test" in all namespaces + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithListCondition(conditions.Count(2)), // correct number + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} + +func TestClusterLevelResourceSuccess(t *testing.T) { + e := Must(Resource("namespace-.*", "*", helpers.GVK("", "v1", "Namespace"), // finds a namespace at the cluster level + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithListCondition(conditions.Count(2)), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} + +func TestExpectedCountFail(t *testing.T) { + e := Must(Resource("test.+", "*", helpers.GVK("", "v1", "Pod"), // finds all pods starting with "test" in all namespaces + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithListCondition(conditions.Count(0)), // wrong number + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} + +func TestAbsentPodSuccess(t *testing.T) { + e := Must(AbsentPod("nonexistent", "*", // tries to find a pod that doesn't exist + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} + +func TestAbsentPodFail(t *testing.T) { + e := Must(AbsentPod("test-1", "namespace-1", // tries to find a pod that shouldn't exist but it does + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} + +func TestAbsentResourceSuccess(t *testing.T) { + expect, err := AbsentResource("nonexistent", "*", helpers.TargetGVK, + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder)) + + e := Must(expect, err) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + err = e.Verify(ctx) + require.NoError(t, err) +} + +func TestAbsentResourceFail(t *testing.T) { + e := Must(AbsentResource("test-1", "namespace-1", helpers.TargetGVK, // tries to find a target that shouldn't exist but it does + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} + +func TestAbsentResourcePassWithRedundantConditions(t *testing.T) { + e := Must(AbsentResource("non-existent", "namespace-1", helpers.TargetGVK, // resource doesn't exist + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithListCondition(conditions.Count(0)), // redundant condition + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} + +func TestUnknownResourceFail(t *testing.T) { + e := Must(Resource("test-1", "namespace-1", helpers.GVK("random.group", "v1", "unknown"), // tries to find a Resource type that is not known + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} + +func TestFailOnNoNamespaceForNamespacedResource(t *testing.T) { + e := Must(Pod("test-1", "", // tries to find a Resource type that is not known + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} + +func TestShouldFailWhenDiscoveryFails(t *testing.T) { + ex, err := Resource("test-1", "namespace-1", helpers.GVK("", "v1", "Pod"), + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(func() (discovery.DiscoveryInterface, error) { + return nil, errors.New("discovery failed") + }), + ) + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + require.Error(t, ex.Verify(ctx)) +} + +func TestShouldFailWhenDynamicFails(t *testing.T) { + ex, err := Resource("test-1", "namespace-1", helpers.GVK("", "v1", "Pod"), + WithDynamicClientBuilder(func() (dynamic.Interface, error) { + return nil, errors.New("dynamic failed") + }), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + ) + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + require.Error(t, ex.Verify(ctx)) +} + +func TestCanUseCustomLogger(t *testing.T) { + called := false + logger := func(format string, args ...interface{}) { + called = true + } + e := Must(Resource("test-1", "namespace-1", helpers.GVK("", "v1", "Pod"), + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithLogger(logger), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) + require.True(t, called) +} + +func TestCanUseCustomTickInterval(t *testing.T) { + e := Must(Resource("test-1", "namespace-1", helpers.GVK("", "v1", "Pod"), + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithTick(time.Millisecond), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) + require.Equal(t, time.Millisecond, e.tick) +} + +func TestAnnotationMathSuccesfulOnResource(t *testing.T) { + e := Must(Resource("test-1", "namespace-1", helpers.GVK("", "v1", "Pod"), + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithCondition( + NewAnnotationMatchCondition("test-annotation", "test-annotation-value"), + ), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} +func TestCombinedSuccessfullConditions(t *testing.T) { + e := Must(Resource("test-.*", "*", helpers.GVK("", "v1", "Pod"), + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithCondition(conditions.All( + NewAnnotationMatchCondition("test-annotation", "test-annotation-value"), + NewAnnotationMatchCondition("management.azure.com/operationId", "test-operation-id"), + )), + WithListCondition(conditions.Count(2)), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} + +func TestOperationIdTargetSuccess(t *testing.T) { + e := Must(Resource("test-1", "namespace-1", helpers.GVK("fabric.symphony", "v1", "Target"), + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithCondition(OperationIdMatchCondition), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.NoError(t, e.Verify(ctx)) +} + +func TestOperationIdTargetFail(t *testing.T) { + e := Must(Resource("test-2", "namespace-2", helpers.GVK("fabric.symphony", "v1", "Target"), + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithCondition(OperationIdMatchCondition), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + require.Error(t, e.Verify(ctx)) +} + +func TestCommonCommonConstructors(t *testing.T) { + _, err := Target("test-1", "namespace-1") + require.NoError(t, err) + + _, err = AbsentTarget("test-1", "namespace-1") + require.NoError(t, err) + + _, err = Instance("test-1", "namespace-1") + require.NoError(t, err) + + _, err = AbsentInstance("test-1", "namespace-1") + require.NoError(t, err) + + _, err = Solution("test-1", "namespace-1") + require.NoError(t, err) + + _, err = AbsentSolution("test-1", "namespace-1") + require.NoError(t, err) +} + +func TestShouldWorkWithGomegaAssersions(t *testing.T) { + mt := internal.NewMockT() + g := gomega.NewWithT(mt) + e := Must(Resource("test-.*", "*", helpers.GVK("", "v1", "Pod"), + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithCondition(conditions.All( + NewAnnotationMatchCondition("test-annotation", "test-annotation-value"), + NewAnnotationMatchCondition("management.azure.com/operationId", "test-operation-id"), + )), + WithListCondition(conditions.Count(2)), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + mt.On("Helper").Return() + g.Eventually(e.AsGomegaSubject()).WithContext(ctx).Should(e.ToGomegaMatcher()) + mt.AssertExpectations(t) + + ctx, cancel = context.WithTimeout(context.Background(), testTimeout) + defer cancel() + mt.On("Fatalf", mock.Anything, mock.Anything).Return() + g.Eventually(e.AsGomegaSubject()).WithContext(ctx).ShouldNot(e.ToGomegaMatcher()) + mt.AssertExpectations(t) +} + +func TestShouldWorkWithInvertedGomegaAssersions(t *testing.T) { + mt := internal.NewMockT() + g := gomega.NewWithT(mt) + e := Must(Resource("test-.*", "*", helpers.GVK("", "v1", "Pod"), + WithDynamicClientBuilder(testDynamicClientBuilder), + WithDiscoveryClientBuilder(testDiscoveryClientBuilder), + WithCondition(conditions.All( + NewAnnotationMatchCondition("test-annotation", "test-annotation-value"), + NewAnnotationMatchCondition("management.azure.com/operationId", "test-operation-id"), + jq.Equality(".spec.containers[0].name", "wrong"), + )), + WithListCondition(conditions.Count(2)), + )) + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + mt.On("Helper").Return() + failingCall := mt.On("Fatalf", mock.Anything, mock.Anything).Return() + g.Eventually(e.AsGomegaSubject()).WithContext(ctx).Should(e.ToGomegaMatcher()) + mt.AssertExpectations(t) + + failingCall.Unset() + g.Eventually(e.AsGomegaSubject()).WithContext(ctx).ShouldNot(e.ToGomegaMatcher()) + mt.AssertExpectations(t) +} diff --git a/packages/testutils/go.mod b/packages/testutils/go.mod new file mode 100644 index 000000000..20556f9ef --- /dev/null +++ b/packages/testutils/go.mod @@ -0,0 +1,150 @@ +module github.com/eclipse-symphony/symphony/packages/testutils + +go 1.19 + +replace github.com/eclipse-symphony/symphony/packages/mage => ../mage + +require ( + github.com/eclipse-symphony/symphony/packages/mage v0.0.0-00010101000000-000000000000 + github.com/google/uuid v1.4.0 + github.com/itchyny/gojq v0.12.13 + github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 + github.com/onsi/gomega v1.30.0 + github.com/stretchr/testify v1.8.4 + helm.sh/helm/v3 v3.10.0 + k8s.io/api v0.25.2 + k8s.io/apimachinery v0.25.2 + k8s.io/client-go v0.25.0 + +) + +require ( + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/BurntSushi/toml v1.1.0 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.2 // indirect + github.com/Masterminds/squirrel v1.5.3 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/VividCortex/ewma v1.1.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/cheggaaa/pb/v3 v3.0.4 // indirect + github.com/containerd/containerd v1.6.6 // indirect + github.com/cyphar/filepath-securejoin v0.2.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/cli v20.10.17+incompatible // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/docker v20.10.17+incompatible // indirect + github.com/docker/docker-credential-helpers v0.6.4 // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-metrics v0.0.1 // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/emicklei/go-restful/v3 v3.8.0 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/go-errors/errors v1.0.1 // indirect + github.com/go-gorp/gorp/v3 v3.0.2 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/swag v0.19.14 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/btree v1.0.1 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/gosuri/uitable v0.0.4 // indirect + github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/itchyny/timefmt-go v0.1.5 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lib/pq v1.10.6 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/magefile/mage v1.15.0 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/princjef/mageutil v1.0.0 // indirect + github.com/prometheus/client_golang v1.12.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rubenv/sql-migrate v1.1.2 // indirect + github.com/russross/blackfriday v1.5.2 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/cobra v1.5.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xlab/treeprint v1.1.0 // indirect + go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect + google.golang.org/grpc v1.47.0 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.25.0 // indirect + k8s.io/apiserver v0.25.0 // indirect + k8s.io/cli-runtime v0.25.0 // indirect + k8s.io/component-base v0.25.0 // indirect + k8s.io/klog/v2 v2.70.1 // indirect + k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect + k8s.io/kubectl v0.25.0 // indirect + k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + oras.land/oras-go v1.2.0 // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/kustomize/api v0.12.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/packages/testutils/go.sum b/packages/testutils/go.sum new file mode 100644 index 000000000..70678a01e --- /dev/null +++ b/packages/testutils/go.sum @@ -0,0 +1,1060 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= +github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= +github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/cheggaaa/pb v2.0.7+incompatible/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/cheggaaa/pb/v3 v3.0.4 h1:QZEPYOj2ix6d5oEg63fbHmpolrnNiwjUsk+h74Yt4bM= +github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= +github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= +github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= +github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= +github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE= +github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= +github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= +github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gorp/gorp/v3 v3.0.2 h1:ULqJXIekoqMx29FI5ekXXFoH1dT2Vc8UhnRzBg+Emz4= +github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= +github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= +github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= +github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= +github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= +github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/itchyny/gojq v0.12.13 h1:IxyYlHYIlspQHHTE0f3cJF0NKDMfajxViuhBLnHd/QU= +github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4= +github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= +github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= +github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= +github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/matryer/is v1.3.0 h1:9qiso3jaJrOe6qBRJRBt2Ldht05qDiFP9le0JOIhRSI= +github.com/matryer/is v1.3.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI= +github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= +github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= +github.com/princjef/mageutil v1.0.0 h1:1OfZcJUMsooPqieOz2ooLjI+uHUo618pdaJsbCXcFjQ= +github.com/princjef/mageutil v1.0.0/go.mod h1:mkShhaUomCYfAoVvTKRcbAs8YSVPdtezI5j6K+VXhrs= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rubenv/sql-migrate v1.1.2 h1:9M6oj4e//owVVHYrFISmY9LBRw6gzkCNmD9MV36tZeQ= +github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= +github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/VividCortex/ewma.v1 v1.1.1/go.mod h1:TekXuFipeiHWiAlO1+wSS23vTcyFau5u3rxXUSXj710= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v2 v2.0.7/go.mod h1:0CiZ1p8pvtxBlQpLXkHuUTpdJ1shm3OqCF1QugkjHL4= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fatih/color.v1 v1.7.0/go.mod h1:P7yosIhqIl/sX8J8UypY5M+dDpD2KmyfP5IRs5v/fo0= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mattn/go-colorable.v0 v0.1.0/go.mod h1:BVJlBXzARQxdi3nZo6f6bnl5yR20/tOL6p+V0KejgSY= +gopkg.in/mattn/go-isatty.v0 v0.0.4/go.mod h1:wt691ab7g0X4ilKZNmMII3egK0bTxl37fEn/Fwbd8gc= +gopkg.in/mattn/go-runewidth.v0 v0.0.4/go.mod h1:BmXejnxvhwdaATwiJbB1vZ2dtXkQKZGu9yLFCZb4msQ= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +helm.sh/helm/v3 v3.10.0 h1:y/MYONZ/bsld9kHwqgBX2uPggnUr5hahpjwt9/jrHlI= +helm.sh/helm/v3 v3.10.0/go.mod h1:paPw0hO5KVfrCMbi1M8+P8xdfBri3IiJiVKATZsFR94= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= +k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= +k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY= +k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E= +k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= +k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= +k8s.io/apiserver v0.25.0 h1:8kl2ifbNffD440MyvHtPaIz1mw4mGKVgWqM0nL+oyu4= +k8s.io/apiserver v0.25.0/go.mod h1:BKwsE+PTC+aZK+6OJQDPr0v6uS91/HWxX7evElAH6xo= +k8s.io/cli-runtime v0.25.0 h1:XBnTc2Fi+w818jcJGzhiJKQuXl8479sZ4FhtV5hVJ1Q= +k8s.io/cli-runtime v0.25.0/go.mod h1:bHOI5ZZInRHhbq12OdUiYZQN8ml8aKZLwQgt9QlLINw= +k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= +k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= +k8s.io/component-base v0.25.0 h1:haVKlLkPCFZhkcqB6WCvpVxftrg6+FK5x1ZuaIDaQ5Y= +k8s.io/component-base v0.25.0/go.mod h1:F2Sumv9CnbBlqrpdf7rKZTmmd2meJq0HizeyY/yAFxk= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= +k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= +k8s.io/kubectl v0.25.0 h1:/Wn1cFqo8ik3iee1EvpxYre3bkWsGLXzLQI6uCCAkQc= +k8s.io/kubectl v0.25.0/go.mod h1:n16ULWsOl2jmQpzt2o7Dud1t4o0+Y186ICb4O+GwKAU= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= +oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= +sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= +sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= +sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/packages/testutils/helpers/README.md b/packages/testutils/helpers/README.md new file mode 100644 index 000000000..a1678124e --- /dev/null +++ b/packages/testutils/helpers/README.md @@ -0,0 +1,102 @@ + + +# helpers + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils/helpers" +``` + +testhelpers contains helpers for tests + +## Index + +- [Variables](<#variables>) +- [func DiscoveryClient\(\) \(discovery.DiscoveryInterface, error\)](<#DiscoveryClient>) +- [func DynamicClient\(\) \(dynamic.Interface, error\)](<#DynamicClient>) +- [func EnsureNamespace\(ctx context.Context, client kubernetes.Interface, namespace string\) error](<#EnsureNamespace>) +- [func Eventually\(ctx context.Context, condition func\(ctx context.Context\) error, tick time.Duration, msg string, args ...interface\{\}\) error](<#Eventually>) +- [func GVK\(group, version, kind string\) schema.GroupVersionKind](<#GVK>) +- [func KubeClient\(\) \(kubernetes.Interface, error\)](<#KubeClient>) +- [func RestConfig\(\) \(\*rest.Config, error\)](<#RestConfig>) + + +## Variables + + + +```go +var ( + TargetGVK = GVK("fabric.symphony", "v1", "Target") + InstanceGVK = GVK("solution.symphony", "v1", "Instance") + SolutionGVK = GVK("solution.symphony", "v1", "Solution") + ConfigMapGVK = GVK("", "v1", "ConfigMap") + PodGVK = GVK("", "v1", "Pod") + NamespaceGVK = GVK("", "v1", "Namespace") + ClusterRoleGVK = GVK("rbac.authorization.k8s.io", "v1", "ClusterRole") +) +``` + + +## func [DiscoveryClient]() + +```go +func DiscoveryClient() (discovery.DiscoveryInterface, error) +``` + +DiscoveryClient returns the discovery client from the default kube config + + +## func [DynamicClient]() + +```go +func DynamicClient() (dynamic.Interface, error) +``` + +DynamicClient returns the dynamic client from the default kube config + + +## func [EnsureNamespace]() + +```go +func EnsureNamespace(ctx context.Context, client kubernetes.Interface, namespace string) error +``` + +EnsureNamespace ensures that the namespace exists. If it does not exist, it creates it. + + +## func [Eventually]() + +```go +func Eventually(ctx context.Context, condition func(ctx context.Context) error, tick time.Duration, msg string, args ...interface{}) error +``` + + + + +## func [GVK]() + +```go +func GVK(group, version, kind string) schema.GroupVersionKind +``` + +GVK creates a GroupVersionKind + + +## func [KubeClient]() + +```go +func KubeClient() (kubernetes.Interface, error) +``` + +KubeClient returns the kubectl client from the default kube config + + +## func [RestConfig]() + +```go +func RestConfig() (*rest.Config, error) +``` + +RestConfig returns the default kube config + +Generated by [gomarkdoc]() diff --git a/packages/testutils/helpers/eventually.go b/packages/testutils/helpers/eventually.go new file mode 100644 index 000000000..9617ddc4d --- /dev/null +++ b/packages/testutils/helpers/eventually.go @@ -0,0 +1,74 @@ +package helpers + +import ( + "context" + "fmt" + "strings" + "time" +) + +type ( + compoundingError struct { + errors []error + msg string + } +) + +// Error implements error. +func (c *compoundingError) Error() string { + finalMessage := strings.Builder{} + if c.msg != "" { + finalMessage.WriteString(c.msg) + } else { + finalMessage.WriteString("Compounding Error") + } + + if len(c.errors) != 0 { + finalMessage.WriteString(":\n") + } + for _, err := range c.errors { + finalMessage.WriteString(fmt.Sprintf("- %v\n", err)) + } + + return finalMessage.String() +} + +var _ error = &compoundingError{} + +func Eventually(ctx context.Context, condition func(ctx context.Context) error, tick time.Duration, msg string, args ...interface{}) error { + errs := make([]error, 0) + errorBuilder := func() error { + return &compoundingError{ + errors: errs, + msg: fmt.Sprintf(msg, args...), + } + } + + select { + case <-ctx.Done(): + return errorBuilder() + default: + } + + if err := condition(ctx); err == nil { + return nil + } else { + errs = append(errs, err) + } + + ticker := time.NewTicker(tick) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return errorBuilder() + case <-ticker.C: + err := condition(ctx) + if err == nil { + return nil + } + errs = append(errs, err) + } + } +} diff --git a/packages/testutils/helpers/eventually_test.go b/packages/testutils/helpers/eventually_test.go new file mode 100644 index 000000000..ee502e662 --- /dev/null +++ b/packages/testutils/helpers/eventually_test.go @@ -0,0 +1,58 @@ +package helpers + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestEventually_Success(t *testing.T) { + condition := func(ctx context.Context) error { + return nil + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) + defer cancel() + err := Eventually(ctx, condition, 1*time.Millisecond, "failure message") + require.NoError(t, err) +} + +func TestEventually_SuccessfulOnSubsequentAttempt(t *testing.T) { + count := 0 + condition := func(ctx context.Context) error { + defer func() { count++ }() + if count <= 1 { + return errors.New("test error") + } + return nil + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) + defer cancel() + err := Eventually(ctx, condition, 1*time.Millisecond, "failure message") + require.NoError(t, err) + require.Equal(t, 3, count) +} + +func TestEventually_Failure(t *testing.T) { + condition := func(ctx context.Context) error { + return errors.New("test error") + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) + defer cancel() + err := Eventually(ctx, condition, 1*time.Millisecond, "failure message") + require.Error(t, err) + require.Contains(t, err.Error(), "failure message") + require.Contains(t, err.Error(), "test error") +} + +func TestEventually_FailIfContextIsAlreadyDone(t *testing.T) { + condition := func(ctx context.Context) error { + return nil + } + ctx, cancel := context.WithCancel(context.Background()) + cancel() + err := Eventually(ctx, condition, 1*time.Millisecond, "failure message") + require.Error(t, err) +} diff --git a/packages/testutils/helpers/kubeutil.go b/packages/testutils/helpers/kubeutil.go new file mode 100644 index 000000000..bc38c4f98 --- /dev/null +++ b/packages/testutils/helpers/kubeutil.go @@ -0,0 +1,143 @@ +// testhelpers contains helpers for tests +package helpers + +import ( + "context" + "os" + "path/filepath" + "sync" + + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +var ( + configInst *rest.Config + configInstMutex sync.Mutex + + TargetGVK = GVK("fabric.symphony", "v1", "Target") + InstanceGVK = GVK("solution.symphony", "v1", "Instance") + SolutionGVK = GVK("solution.symphony", "v1", "Solution") + ConfigMapGVK = GVK("", "v1", "ConfigMap") + PodGVK = GVK("", "v1", "Pod") + NamespaceGVK = GVK("", "v1", "Namespace") + ClusterRoleGVK = GVK("rbac.authorization.k8s.io", "v1", "ClusterRole") + + configGetter func(string, string) (*rest.Config, error) = clientcmd.BuildConfigFromFlags + dynamicBuilder func(*rest.Config) (dynamic.Interface, error) = func(config *rest.Config) (dynamic.Interface, error) { + return dynamic.NewForConfig(config) + } + discoveryBuilder func(*rest.Config) (discovery.DiscoveryInterface, error) = func(config *rest.Config) (discovery.DiscoveryInterface, error) { + return discovery.NewDiscoveryClientForConfig(config) + } + kubernetesBuilder func(*rest.Config) (kubernetes.Interface, error) = func(config *rest.Config) (kubernetes.Interface, error) { + return kubernetes.NewForConfig(config) + } +) + +// KubeClient returns the kubectl client from the default kube config +func KubeClient() (kubernetes.Interface, error) { + config, err := RestConfig() + if err != nil { + return nil, err + } + + // create the clientset + clientset, err := kubernetesBuilder(config) + if err != nil { + return nil, err + } + + return clientset, nil +} + +// DiscoveryClient returns the discovery client from the default kube config +func DiscoveryClient() (discovery.DiscoveryInterface, error) { + config, err := RestConfig() + if err != nil { + return nil, err + } + + // create the clientset + client, err := discoveryBuilder(config) + if err != nil { + return nil, err + } + + return client, nil +} + +// DynamicClient returns the dynamic client from the default kube config +func DynamicClient() (dynamic.Interface, error) { + config, err := RestConfig() + if err != nil { + return nil, err + } + + // create the clientset + client, err := dynamicBuilder(config) + if err != nil { + return nil, err + } + + return client, nil +} + +// RestConfig returns the default kube config +func RestConfig() (*rest.Config, error) { + configInstMutex.Lock() + defer configInstMutex.Unlock() + var err error + if configInst != nil { + return configInst, nil + } + homeDir, _ := os.UserHomeDir() + kubeconfigPath := filepath.Join(homeDir, ".kube", "config") + + configInst, err = configGetter("", kubeconfigPath) + if err != nil { + return nil, err + } + return configInst, nil +} + +// EnsureNamespace ensures that the namespace exists. If it does not exist, it creates it. +func EnsureNamespace(ctx context.Context, client kubernetes.Interface, namespace string) error { + _, err := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{}) + if err == nil { + return nil + } + + if kerrors.IsNotFound(err) { + _, err = client.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }, metav1.CreateOptions{}) + if err != nil { + return err + } + + } else { + return err + } + + return nil +} + +// GVK creates a GroupVersionKind +func GVK(group, version, kind string) schema.GroupVersionKind { + return schema.GroupVersionKind{ + Group: group, + Version: version, + Kind: kind, + } +} diff --git a/packages/testutils/helpers/kubeutil_test.go b/packages/testutils/helpers/kubeutil_test.go new file mode 100644 index 000000000..81d85baf7 --- /dev/null +++ b/packages/testutils/helpers/kubeutil_test.go @@ -0,0 +1,181 @@ +package helpers + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/eclipse-symphony/symphony/packages/testutils/internal" + "github.com/stretchr/testify/require" + "k8s.io/client-go/discovery" + fakedisc "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/dynamic" + fakedyn "k8s.io/client-go/dynamic/fake" + "k8s.io/client-go/kubernetes" + fakekube "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/rest" +) + +type ( + kubeutilTestCase struct { + name string + configError bool + builderErr bool + wantErr bool + } +) + +var ( + matrix = []kubeutilTestCase{ + { + name: "should return error if config builder fails and dynamic builder fails", + configError: true, + builderErr: true, + wantErr: true, + }, + { + name: "should return error if config builder fails", + configError: true, + wantErr: true, + }, + { + name: "should return error if dynamic builder fails", + builderErr: true, + wantErr: true, + }, + { + name: "should return dynamic client", + }, + } + testTimeout = 1 * time.Millisecond +) + +func setDynBuilder(shouldError bool) { + dynamicBuilder = func(*rest.Config) (dynamic.Interface, error) { + if shouldError { + return nil, errors.New("error") + } + return &fakedyn.FakeDynamicClient{}, nil + } +} + +func setConfigBuilder(shouldError bool) { + configGetter = func(string, string) (*rest.Config, error) { + if shouldError { + return nil, errors.New("error") + } + return &rest.Config{}, nil + } +} + +func setDiscoveryBuilder(shouldError bool) { + discoveryBuilder = func(*rest.Config) (discovery.DiscoveryInterface, error) { + if shouldError { + return nil, errors.New("error") + } + return &fakedisc.FakeDiscovery{}, nil + } +} + +func setKubeBuilder(shouldError bool) { + kubernetesBuilder = func(*rest.Config) (kubernetes.Interface, error) { + if shouldError { + return nil, errors.New("error") + } + fakekube.NewSimpleClientset() + return &fakekube.Clientset{}, nil + } +} + +func TestKubernetes(t *testing.T) { + for _, tt := range matrix { + t.Run(tt.name, func(t *testing.T) { + t.Cleanup(cleanup) + setKubeBuilder(tt.builderErr) + setConfigBuilder(tt.configError) + _, err := KubeClient() + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestDiscovery(t *testing.T) { + for _, tt := range matrix { + t.Run(tt.name, func(t *testing.T) { + t.Cleanup(cleanup) + setDiscoveryBuilder(tt.builderErr) + setConfigBuilder(tt.configError) + _, err := DiscoveryClient() + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestDynamic(t *testing.T) { + for _, tt := range matrix { + t.Run(tt.name, func(t *testing.T) { + t.Cleanup(cleanup) + setDynBuilder(tt.builderErr) + setConfigBuilder(tt.configError) + _, err := DynamicClient() + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} + +func TestRestConfig_Success(t *testing.T) { + t.Cleanup(cleanup) + setConfigBuilder(false) + _, err := RestConfig() + require.NoError(t, err) +} +func TestRestConfig_ReturnsFromCache(t *testing.T) { + t.Cleanup(cleanup) + configInst = &rest.Config{} + setConfigBuilder(false) + returned, err := RestConfig() + require.NoError(t, err) + require.Equal(t, configInst, returned) +} + +func TestRestConfig_Faile(t *testing.T) { + t.Cleanup(cleanup) + setConfigBuilder(true) + _, err := RestConfig() + require.Error(t, err) +} + +func TestEnsureNamespaceSucceedsWhenNamespaceAlreadyExist(t *testing.T) { + t.Cleanup(cleanup) + client := fakekube.NewSimpleClientset(internal.Namespace("test")) // initializes the fake client with the namespace object + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + err := EnsureNamespace(ctx, client, "test") + require.NoError(t, err) +} + +func TestEnsureNamespaceSuccessWhenNamespaceDoesntExist(t *testing.T) { + t.Cleanup(cleanup) + client := fakekube.NewSimpleClientset() + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + err := EnsureNamespace(ctx, client, "test") + require.NoError(t, err) +} + +func cleanup() { + configInst = nil +} diff --git a/packages/testutils/internal/README.md b/packages/testutils/internal/README.md new file mode 100644 index 000000000..00d704a60 --- /dev/null +++ b/packages/testutils/internal/README.md @@ -0,0 +1,125 @@ + + +# internal + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils/internal" +``` + +## Index + +- [func GenerateTestApiResourceList\(\) \[\]\*metav1.APIResourceList](<#GenerateTestApiResourceList>) +- [func Namespace\(name string\) \*corev1.Namespace](<#Namespace>) +- [func OutOfSyncResource\(name, namespace string, gvk schema.GroupVersionKind\) \*unstructured.Unstructured](<#OutOfSyncResource>) +- [func Pod\(name, namespace string\) \*corev1.Pod](<#Pod>) +- [func Resource\(name, namespace string, gvk schema.GroupVersionKind\) \*unstructured.Unstructured](<#Resource>) +- [func Target\(name, namespace string\) \*unstructured.Unstructured](<#Target>) +- [type MockT](<#MockT>) + - [func NewMockT\(\) \*MockT](<#NewMockT>) + - [func \(\_m \*MockT\) Errorf\(format string, args ...interface\{\}\)](<#MockT.Errorf>) + - [func \(\_m \*MockT\) Fatalf\(format string, args ...interface\{\}\)](<#MockT.Fatalf>) + - [func \(\_m \*MockT\) Helper\(\)](<#MockT.Helper>) + + + +## func [GenerateTestApiResourceList]() + +```go +func GenerateTestApiResourceList() []*metav1.APIResourceList +``` + + + + +## func [Namespace]() + +```go +func Namespace(name string) *corev1.Namespace +``` + + + + +## func [OutOfSyncResource]() + +```go +func OutOfSyncResource(name, namespace string, gvk schema.GroupVersionKind) *unstructured.Unstructured +``` + + + + +## func [Pod]() + +```go +func Pod(name, namespace string) *corev1.Pod +``` + + + + +## func [Resource]() + +```go +func Resource(name, namespace string, gvk schema.GroupVersionKind) *unstructured.Unstructured +``` + + + + +## func [Target]() + +```go +func Target(name, namespace string) *unstructured.Unstructured +``` + + + + +## type [MockT]() + + + +```go +type MockT struct { + mock.Mock +} +``` + + +### func [NewMockT]() + +```go +func NewMockT() *MockT +``` + + + + +### func \(\*MockT\) [Errorf]() + +```go +func (_m *MockT) Errorf(format string, args ...interface{}) +``` + +Errorf provides a mock function with given fields: format, args + + +### func \(\*MockT\) [Fatalf]() + +```go +func (_m *MockT) Fatalf(format string, args ...interface{}) +``` + + + + +### func \(\*MockT\) [Helper]() + +```go +func (_m *MockT) Helper() +``` + +Helper provides a mock function + +Generated by [gomarkdoc]() diff --git a/packages/testutils/internal/context/README.md b/packages/testutils/internal/context/README.md new file mode 100644 index 000000000..73b1a0e8d --- /dev/null +++ b/packages/testutils/internal/context/README.md @@ -0,0 +1,13 @@ + + +# context + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils/internal/context" +``` + +## Index + + + +Generated by [gomarkdoc]() diff --git a/packages/testutils/internal/context/context.go b/packages/testutils/internal/context/context.go new file mode 100644 index 000000000..4937e913a --- /dev/null +++ b/packages/testutils/internal/context/context.go @@ -0,0 +1,56 @@ +package context + +import ( + "context" +) + +type ( + key int + + nestContext struct { + context.Context + level int + } +) + +const ( + nestKey key = iota +) + +func (c *nestContext) Level() int { + return c.level +} + +func (c *nestContext) Nested() *nestContext { + return &nestContext{ + Context: c, + level: c.level + 2, + } +} + +func (c *nestContext) Value(key any) any { + if key == nestKey { + return c + } + return c.Context.Value(key) +} + +// From returns a context.Context that wraps the given context. This context +// allows to track the nesting level of the context. Useful for logging. +func From(ctx context.Context) *nestContext { + if ctx == nil { + return &nestContext{ + Context: context.Background(), + } + } + if ec, ok := ctx.(*nestContext); ok { + return ec + } + ec, ok := ctx.Value(nestKey).(*nestContext) + if !ok { + return &nestContext{ + Context: ctx, + } + } + return ec +} diff --git a/packages/testutils/internal/context/context_test.go b/packages/testutils/internal/context/context_test.go new file mode 100644 index 000000000..4faece490 --- /dev/null +++ b/packages/testutils/internal/context/context_test.go @@ -0,0 +1,37 @@ +package context + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestContext(t *testing.T) { + c, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + + ctx := From(c) + require.Equal(t, 0, ctx.Level()) + + ctx = ctx.Nested() + require.Equal(t, 2, ctx.Level()) + + c, cancel = context.WithTimeout(ctx, time.Millisecond) // embeds regular context + defer cancel() + + c, cancel = context.WithTimeout(c, time.Millisecond) // embeds expectation context further + defer cancel() + + ctx = From(c) + require.Equal(t, 2, ctx.Level()) + + ctx = From(nil) + require.NotNil(t, ctx) + require.Equal(t, 0, ctx.Level()) + + ctx2 := From(ctx) + require.Equal(t, ctx2, ctx) + +} diff --git a/packages/testutils/internal/helper.go b/packages/testutils/internal/helper.go new file mode 100644 index 000000000..f4e401dae --- /dev/null +++ b/packages/testutils/internal/helper.go @@ -0,0 +1,161 @@ +package internal + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func Pod(name, namespace string) *corev1.Pod { + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + "management.azure.com/operationId": "test-operation-id", + "test-annotation": "test-annotation-value", + }, + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Pod", + APIVersion: "v1", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: name, + Image: fmt.Sprintf("image/%s", name), + }, + }, + }, + } +} + +func Resource(name, namespace string, gvk schema.GroupVersionKind) *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": name, + "namespace": namespace, + "annotations": map[string]interface{}{ + "management.azure.com/operationId": "test-operation-id", + "test-annotation": "test-annotation-value", + }, + }, + "apiVersion": gvk.GroupVersion().String(), + "kind": gvk.Kind, + }, + } +} + +func Target(name, namespace string) *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": name, + "namespace": namespace, + "annotations": map[string]interface{}{ + "management.azure.com/operationId": "test-operation-id", + "test-annotation": "test-annotation-value", + }, + }, + "apiVersion": "fabric.symphony/v1", + "kind": "Target", + "status": map[string]interface{}{ + "provisioningStatus": map[string]interface{}{ + "status": "Succeeded", + "operationId": "test-operation-id", + }, + }, + }, + } +} + +func Namespace(name string) *corev1.Namespace { + return &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Namespace", + APIVersion: "v1", + }, + } +} + +func OutOfSyncResource(name, namespace string, gvk schema.GroupVersionKind) *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": name, + "namespace": namespace, + "annotations": map[string]interface{}{ + "management.azure.com/operationId": "test-operation-new", + "test-annotation": "test-annotation-value", + }, + }, + "apiVersion": gvk.GroupVersion().String(), + "kind": gvk.Kind, + "status": map[string]interface{}{ + "provisioningStatus": map[string]interface{}{ + "status": "Succeeded", + "operationId": "test-operation-id-old", // this is the old operation id + }, + }, + }, + } +} + +func GenerateTestApiResourceList() []*metav1.APIResourceList { + // we want to test the following resources: + // - core/v1: Pods, ConfigMaps, Namespaces + // - orchestrator.iotoperations.azure.com/v1: Targets + + return []*metav1.APIResourceList{ + { + GroupVersion: "v1", + APIResources: []metav1.APIResource{ + { + Name: "pods", + Namespaced: true, + Kind: "Pod", + SingularName: "pod", + Group: "", + Version: "v1", + }, + { + Name: "configmaps", + Namespaced: true, + Kind: "ConfigMap", + SingularName: "configmap", + Group: "", + Version: "v1", + }, + { + Name: "namespaces", + Namespaced: false, + Kind: "Namespace", + SingularName: "namespace", + Group: "", + Version: "v1", + }, + }, + }, + { + GroupVersion: "fabric.symphony/v1", + APIResources: []metav1.APIResource{ + { + Name: "targets", + Namespaced: true, + Kind: "Target", + SingularName: "target", + Group: "fabric.symphony", + Version: "v1", + }, + }, + }, + } +} diff --git a/packages/testutils/internal/helper_test.go b/packages/testutils/internal/helper_test.go new file mode 100644 index 000000000..b62c82540 --- /dev/null +++ b/packages/testutils/internal/helper_test.go @@ -0,0 +1,58 @@ +package internal + +import ( + "testing" + + "github.com/eclipse-symphony/symphony/packages/testutils/helpers" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestPod(t *testing.T) { + p := Pod("test-pod", "test-namespace") + require.Equal(t, "test-pod", p.GetName()) + require.Equal(t, "test-namespace", p.GetNamespace()) + require.Equal(t, helpers.PodGVK, p.GroupVersionKind()) +} + +func TestTarget(t *testing.T) { + tgt := Target("test", "test-namespace") + require.Equal(t, "test", tgt.GetName()) + require.Equal(t, "test-namespace", tgt.GetNamespace()) + require.Equal(t, helpers.TargetGVK, tgt.GroupVersionKind()) +} +func TestOutOfSyncTarget(t *testing.T) { + tgt := OutOfSyncResource("test", "test-namespace", helpers.TargetGVK) + require.Equal(t, "test", tgt.GetName()) + require.Equal(t, "test-namespace", tgt.GetNamespace()) + require.Equal(t, helpers.TargetGVK, tgt.GroupVersionKind()) +} + +func TestApiResourceListGenerator(t *testing.T) { + arl := GenerateTestApiResourceList() + require.Len(t, arl, 2) +} + +func TestResource(t *testing.T) { + r := Resource("test", "test-namespace", helpers.InstanceGVK) + require.Equal(t, "test", r.GetName()) + require.Equal(t, "test-namespace", r.GetNamespace()) + require.Equal(t, helpers.InstanceGVK, r.GroupVersionKind()) +} + +func TestNamespace(t *testing.T) { + ns := Namespace("test") + require.Equal(t, "test", ns.GetName()) + require.Equal(t, helpers.NamespaceGVK, ns.GroupVersionKind()) +} + +func TestMockT(t *testing.T) { + m := NewMockT() + m.On("Helper") + m.On("Errorf", mock.Anything, mock.Anything) + m.On("Fatalf", mock.Anything, mock.Anything) + m.Helper() + m.Errorf("test") + m.Fatalf("test") + m.AssertExpectations(t) +} diff --git a/packages/testutils/internal/mockt.go b/packages/testutils/internal/mockt.go new file mode 100644 index 000000000..268cd9849 --- /dev/null +++ b/packages/testutils/internal/mockt.go @@ -0,0 +1,25 @@ +package internal + +import "github.com/stretchr/testify/mock" + +type MockT struct { + mock.Mock +} + +func NewMockT() *MockT { + return &MockT{} +} + +// Errorf provides a mock function with given fields: format, args +func (_m *MockT) Errorf(format string, args ...interface{}) { + _m.Called(format, args) +} + +func (_m *MockT) Fatalf(format string, args ...interface{}) { + _m.Called(format, args) +} + +// Helper provides a mock function +func (_m *MockT) Helper() { + _m.Called() +} diff --git a/packages/testutils/logger/README.md b/packages/testutils/logger/README.md new file mode 100644 index 000000000..bdb524170 --- /dev/null +++ b/packages/testutils/logger/README.md @@ -0,0 +1,43 @@ + + +# logger + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils/logger" +``` + +## Index + +- [func SetDefaultLogger\(logger Logger\)](<#SetDefaultLogger>) +- [type Logger](<#Logger>) + - [func GetDefaultLogger\(\) Logger](<#GetDefaultLogger>) + + + +## func [SetDefaultLogger]() + +```go +func SetDefaultLogger(logger Logger) +``` + + + + +## type [Logger]() + + + +```go +type Logger = func(format string, args ...interface{}) +``` + + +### func [GetDefaultLogger]() + +```go +func GetDefaultLogger() Logger +``` + + + +Generated by [gomarkdoc]() diff --git a/packages/testutils/logger/logger.go b/packages/testutils/logger/logger.go new file mode 100644 index 000000000..4f5f17897 --- /dev/null +++ b/packages/testutils/logger/logger.go @@ -0,0 +1,19 @@ +package logger + +import "fmt" + +type ( + Logger = func(format string, args ...interface{}) +) + +var defaultLogger Logger = func(format string, args ...interface{}) { + fmt.Printf(format, args...) +} + +func SetDefaultLogger(logger Logger) { + defaultLogger = logger +} + +func GetDefaultLogger() Logger { + return defaultLogger +} diff --git a/packages/testutils/logger/logger_test.go b/packages/testutils/logger/logger_test.go new file mode 100644 index 000000000..db3b512e4 --- /dev/null +++ b/packages/testutils/logger/logger_test.go @@ -0,0 +1,23 @@ +package logger + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDefault(t *testing.T) { + require.NotPanics(t, func() { + GetDefaultLogger()("test") + }) +} + +func TestCustomLogger(t *testing.T) { + called := false + fn := func(string, ...interface{}) { + called = true + } + SetDefaultLogger(fn) + GetDefaultLogger()("test") + require.True(t, called) +} diff --git a/packages/testutils/magefile.go b/packages/testutils/magefile.go new file mode 100644 index 000000000..8c9c13dc0 --- /dev/null +++ b/packages/testutils/magefile.go @@ -0,0 +1,9 @@ +//go:build mage +// +build mage + +package main + +import ( + // mage:import + _ "github.com/eclipse-symphony/symphony/packages/mage" +) diff --git a/packages/testutils/types/README.md b/packages/testutils/types/README.md new file mode 100644 index 000000000..e7c9335f7 --- /dev/null +++ b/packages/testutils/types/README.md @@ -0,0 +1,68 @@ + + +# types + +```go +import "github.com/eclipse-symphony/symphony/packages/testutils/types" +``` + +## Index + +- [type Condition](<#Condition>) +- [type Expectation](<#Expectation>) +- [type GomegaEventuallySubject](<#GomegaEventuallySubject>) +- [type GomegaMatchable](<#GomegaMatchable>) + + + +## type [Condition]() + +Condition is a way to define a condition for a resource. It is used to verify whether a resource is in the expected state. It's meant to be used in conjunction with an Expectation. The Expectation is responsible for retrieving the resource and passing it to the Condition to verify that the resource is as expected. + +```go +type Condition interface { + + // IsSatisfiedBy returns `nil` if the condition is satisfied by the given resource. + IsSatisfiedBy(ctx context.Context, resource interface{}) error + // contains filtered or unexported methods +} +``` + + +## type [Expectation]() + +Expectation is a way to define expectations for a resource. It is used to verify whether a resource is in the expected state. It's responsible for retrieving the resource and verifying that the resource is as expected. + +```go +type Expectation interface { + + // Verify runs the expactation. It returns an error if the expectation is not met. + Verify(ctx context.Context) error + // contains filtered or unexported methods +} +``` + + +## type [GomegaEventuallySubject]() + + + +```go +type GomegaEventuallySubject interface { + GomegaMatchable + AsGomegaSubject() func(context.Context) (interface{}, error) +} +``` + + +## type [GomegaMatchable]() + + + +```go +type GomegaMatchable interface { + ToGomegaMatcher() types.GomegaMatcher +} +``` + +Generated by [gomarkdoc]() diff --git a/packages/testutils/types/types.go b/packages/testutils/types/types.go new file mode 100644 index 000000000..e0a6850ee --- /dev/null +++ b/packages/testutils/types/types.go @@ -0,0 +1,39 @@ +package types + +import ( + "context" + + "github.com/onsi/gomega/types" +) + +type ( + id interface { + Id() string + Description() string + } + + // Expectation is a way to define expectations for a resource. It is used to verify whether a resource is in the + // expected state. It's responsible for retrieving the resource and verifying that the resource is as expected. + Expectation interface { + id + // Verify runs the expactation. It returns an error if the expectation is not met. + Verify(ctx context.Context) error + } + + // Condition is a way to define a condition for a resource. It is used to verify whether a resource is in the + // expected state. It's meant to be used in conjunction with an Expectation. The Expectation is responsible for + // retrieving the resource and passing it to the Condition to verify that the resource is as expected. + Condition interface { + id + // IsSatisfiedBy returns `nil` if the condition is satisfied by the given resource. + IsSatisfiedBy(ctx context.Context, resource interface{}) error + } + + GomegaMatchable interface { + ToGomegaMatcher() types.GomegaMatcher + } + GomegaEventuallySubject interface { + GomegaMatchable + AsGomegaSubject() func(context.Context) (interface{}, error) + } +) diff --git a/test/integration/go.mod b/test/integration/go.mod index 95fa581c8..f21eb13b1 100644 --- a/test/integration/go.mod +++ b/test/integration/go.mod @@ -2,58 +2,156 @@ module github.com/eclipse-symphony/symphony/test/integration go 1.20 -replace github.com/eclipse-symphony/symphony/coa => ../../coa - -replace github.com/eclipse-symphony/symphony/api => ../../api +replace ( + github.com/eclipse-symphony/symphony/api => ../../api + github.com/eclipse-symphony/symphony/coa => ../../coa + github.com/eclipse-symphony/symphony/k8s => ../../k8s + github.com/eclipse-symphony/symphony/packages/mage => ../../packages/mage + github.com/eclipse-symphony/symphony/packages/testutils => ../../packages/testutils +) require ( + github.com/eclipse-symphony/symphony/api v0.0.0-00010101000000-000000000000 + github.com/eclipse-symphony/symphony/coa v0.0.0 + github.com/eclipse-symphony/symphony/packages/testutils v0.0.0-00010101000000-000000000000 + github.com/onsi/ginkgo/v2 v2.13.1 + github.com/onsi/gomega v1.30.0 github.com/princjef/mageutil v1.0.0 github.com/stretchr/testify v1.8.4 gopkg.in/yaml.v2 v2.4.0 - k8s.io/apimachinery v0.25.0 + k8s.io/apimachinery v0.25.2 k8s.io/client-go v0.25.0 ) require ( + github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/BurntSushi/toml v1.1.0 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.2 // indirect + github.com/Masterminds/squirrel v1.5.3 // indirect + github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/containerd/containerd v1.7.0-beta.0 // indirect + github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/docker/cli v24.0.6+incompatible // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v20.10.24+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-metrics v0.0.1 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/color v1.13.0 // indirect + github.com/go-errors/errors v1.0.1 // indirect + github.com/go-gorp/gorp/v3 v3.0.2 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/btree v1.0.1 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.4.0 // indirect - github.com/imdario/mergo v0.3.12 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/gosuri/uitable v0.0.4 // indirect + github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/itchyny/gojq v0.12.13 // indirect + github.com/itchyny/timefmt-go v0.1.5 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.16.3 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/lib/pq v1.10.6 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rubenv/sql-migrate v1.1.2 // indirect + github.com/russross/blackfriday v1.6.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xlab/treeprint v1.1.0 // indirect + go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + golang.org/x/tools v0.14.0 // indirect google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/grpc v1.61.1 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.25.0 // indirect + helm.sh/helm/v3 v3.10.0 // indirect + k8s.io/api v0.25.2 // indirect + k8s.io/apiextensions-apiserver v0.25.0 // indirect + k8s.io/apiserver v0.25.0 // indirect + k8s.io/cli-runtime v0.25.0 // indirect + k8s.io/component-base v0.25.0 // indirect k8s.io/klog/v2 v2.90.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect + k8s.io/kubectl v0.25.0 // indirect + k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect + oras.land/oras-go v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/kustomize/api v0.12.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect -) \ No newline at end of file +) diff --git a/test/integration/go.sum b/test/integration/go.sum index 492a0444e..a7754adeb 100644 --- a/test/integration/go.sum +++ b/test/integration/go.sum @@ -1,23 +1,175 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= +github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/hcsshim v0.10.0-rc.7 h1:HBytQPxcv8Oy4244zbQbe6hnOnx544eL5QPUqhJldz8= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/cheggaaa/pb v2.0.7+incompatible/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/containerd/cgroups v1.0.5-0.20220816231112-7083cd60b721 h1:qWq0iv560E8jXZKwWipx3Xot0dYPyfKBeDNfRwYth/U= +github.com/containerd/containerd v1.7.0-beta.0 h1:TmelrlMneeWvAbqqTB9XQ3yCc3voPrBT/k80D8kj5dw= +github.com/containerd/containerd v1.7.0-beta.0/go.mod h1:d+x3kmR4hnXSGTCbLRpBFnP5lOEjqm7dLwZ4UCz01WI= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= +github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= +github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= +github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= +github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= +github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gorp/gorp/v3 v3.0.2 h1:ULqJXIekoqMx29FI5ekXXFoH1dT2Vc8UhnRzBg+Emz4= +github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -27,242 +179,887 @@ github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTr github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= +github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= +github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= +github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= +github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= +github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/itchyny/gojq v0.12.13 h1:IxyYlHYIlspQHHTE0f3cJF0NKDMfajxViuhBLnHd/QU= +github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4= +github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= +github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= +github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= +github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= +github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/matryer/is v1.3.0 h1:9qiso3jaJrOe6qBRJRBt2Ldht05qDiFP9le0JOIhRSI= github.com/matryer/is v1.3.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= -github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI= +github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= +github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU= +github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= +github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= github.com/princjef/mageutil v1.0.0 h1:1OfZcJUMsooPqieOz2ooLjI+uHUo618pdaJsbCXcFjQ= github.com/princjef/mageutil v1.0.0/go.mod h1:mkShhaUomCYfAoVvTKRcbAs8YSVPdtezI5j6K+VXhrs= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rubenv/sql-migrate v1.1.2 h1:9M6oj4e//owVVHYrFISmY9LBRw6gzkCNmD9MV36tZeQ= +github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= +github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9 h1:lNtcVz/3bOstm7Vebox+5m3nLh/BYWnhmc3AhXOW6oI= +golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/VividCortex/ewma.v1 v1.1.1/go.mod h1:TekXuFipeiHWiAlO1+wSS23vTcyFau5u3rxXUSXj710= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v2 v2.0.7/go.mod h1:0CiZ1p8pvtxBlQpLXkHuUTpdJ1shm3OqCF1QugkjHL4= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fatih/color.v1 v1.7.0/go.mod h1:P7yosIhqIl/sX8J8UypY5M+dDpD2KmyfP5IRs5v/fo0= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mattn/go-colorable.v0 v0.1.0/go.mod h1:BVJlBXzARQxdi3nZo6f6bnl5yR20/tOL6p+V0KejgSY= gopkg.in/mattn/go-isatty.v0 v0.0.4/go.mod h1:wt691ab7g0X4ilKZNmMII3egK0bTxl37fEn/Fwbd8gc= gopkg.in/mattn/go-runewidth.v0 v0.0.4/go.mod h1:BmXejnxvhwdaATwiJbB1vZ2dtXkQKZGu9yLFCZb4msQ= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +helm.sh/helm/v3 v3.10.0 h1:y/MYONZ/bsld9kHwqgBX2uPggnUr5hahpjwt9/jrHlI= +helm.sh/helm/v3 v3.10.0/go.mod h1:paPw0hO5KVfrCMbi1M8+P8xdfBri3IiJiVKATZsFR94= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= -k8s.io/api v0.27.3 h1:yR6oQXXnUEBWEWcvPWS0jQL575KoAboQPfJAuKNrw5Y= -k8s.io/api v0.27.3/go.mod h1:C4BNvZnQOF7JA/0Xed2S+aUyJSfTGkGFxLXz9MnpIpg= -k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= -k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= -k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= -k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= +k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= +k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY= +k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E= +k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= +k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= +k8s.io/apiserver v0.25.0 h1:8kl2ifbNffD440MyvHtPaIz1mw4mGKVgWqM0nL+oyu4= +k8s.io/apiserver v0.25.0/go.mod h1:BKwsE+PTC+aZK+6OJQDPr0v6uS91/HWxX7evElAH6xo= +k8s.io/cli-runtime v0.25.0 h1:XBnTc2Fi+w818jcJGzhiJKQuXl8479sZ4FhtV5hVJ1Q= +k8s.io/cli-runtime v0.25.0/go.mod h1:bHOI5ZZInRHhbq12OdUiYZQN8ml8aKZLwQgt9QlLINw= k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= -k8s.io/client-go v0.27.3 h1:7dnEGHZEJld3lYwxvLl7WoehK6lAq7GvgjxpA3nv1E8= -k8s.io/client-go v0.27.3/go.mod h1:2MBEKuTo6V1lbKy3z1euEGnhPfGZLKTS9tiJ2xodM48= +k8s.io/component-base v0.25.0 h1:haVKlLkPCFZhkcqB6WCvpVxftrg6+FK5x1ZuaIDaQ5Y= +k8s.io/component-base v0.25.0/go.mod h1:F2Sumv9CnbBlqrpdf7rKZTmmd2meJq0HizeyY/yAFxk= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= -k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= -k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kubectl v0.25.0 h1:/Wn1cFqo8ik3iee1EvpxYre3bkWsGLXzLQI6uCCAkQc= +k8s.io/kubectl v0.25.0/go.mod h1:n16ULWsOl2jmQpzt2o7Dud1t4o0+Y186ICb4O+GwKAU= +k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= +k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= +oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= +sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= +sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= +sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= \ No newline at end of file +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/test/integration/lib/shell/shell.go b/test/integration/lib/shell/shell.go new file mode 100644 index 000000000..89edca00b --- /dev/null +++ b/test/integration/lib/shell/shell.go @@ -0,0 +1,79 @@ +package shell + +import ( + "context" + "fmt" + "os/exec" + "path/filepath" + "runtime" + + ginkgo "github.com/onsi/ginkgo/v2" +) + +var localenvPath string + +func init() { + _, filename, _, _ := runtime.Caller(0) + dir := filepath.Dir(filename) + localenvPath = filepath.Join(dir, "../../../localenv") +} + +// Run a command in the context of /localenv +func LocalenvCmd(ctx context.Context, cmd string) error { + // first print the working directory + return Exec(ctx, fmt.Sprintf("cd %s && %s", localenvPath, cmd)) +} + +// Run a command with | or other things that do not work in shellcmd +func Exec(ctx context.Context, cmd string) error { + execCmd := getShellCmd(ctx, cmd) + return execCmd.Run() +} + +func ExecAll(ctx context.Context, cmds ...string) error { + for _, cmd := range cmds { + err := Exec(ctx, cmd) + if err != nil { + return err + } + } + return nil +} + +func Output(ctx context.Context, cmd string) ([]byte, error) { + execCmd := getShellCmd(ctx, cmd) + + return execCmd.Output() +} + +func PipeInExec(ctx context.Context, cmd string, stdin []byte) error { + execCmd := getShellCmd(ctx, cmd) + writer, err := execCmd.StdinPipe() + if err != nil { + return err + } + writer.Write(stdin) + writer.Close() + + return execCmd.Run() +} + +func PipeInForOutput(ctx context.Context, cmd string, stdin []byte) ([]byte, error) { + execCmd := getShellCmd(ctx, cmd) + writer, err := execCmd.StdinPipe() + if err != nil { + return nil, err + } + writer.Write(stdin) + writer.Close() + + return execCmd.Output() +} + +func getShellCmd(ctx context.Context, cmd string) *exec.Cmd { + ginkgo.GinkgoWriter.Printf("\033[35m>\033[0m\033[1m %s\033[0m\n", cmd) + execCmd := exec.CommandContext(ctx, "sh", "-c", cmd) + execCmd.Stdout = ginkgo.GinkgoWriter + execCmd.Stderr = ginkgo.GinkgoWriter + return execCmd +} diff --git a/test/integration/lib/testhelpers/component_map.go b/test/integration/lib/testhelpers/component_map.go new file mode 100644 index 000000000..9d8c3f516 --- /dev/null +++ b/test/integration/lib/testhelpers/component_map.go @@ -0,0 +1,558 @@ +package testhelpers + +type Map = map[string]interface{} +type Array = []interface{} + +// Todo: Switch over to symphony core types from the /k8s/api folder +var ( + ComponetsMap = map[string]ComponentSpec{ + // A simple chart that deploy a single pod, a configmap and a serviceaccount + "simple-chart-1": { + Name: "simple-chart-1", + Properties: Map{ + "chart": Map{ + "repo": "symphonycr.azurecr.io/simple-chart", + "version": "0.3.0", + }, + }, + Type: "helm.v3", + }, + + "simple-chart-1-nonexistent": { + Name: "simple-chart-1", + Properties: Map{ + "chart": Map{ + "repo": "symphonycr.azurecr.io/simple-chart", + "version": "0.3.0-nonexistent", + }, + }, + Type: "helm.v3", + }, + + "simple-chart-1-with-values": { + Name: "simple-chart-1", + Properties: Map{ + "chart": Map{ + "repo": "symphonycr.azurecr.io/simple-chart", + "version": "0.3.0", + }, + "values": Map{ + "configData": Map{ + "key": "value", + }, + }, + }, + Type: "helm.v3", + }, + + // A simple chart that deploy a single pod, a configmap and a serviceaccount + "simple-chart-2": { + Name: "simple-chart-2", + Properties: Map{ + "chart": Map{ + "repo": "symphonycr.azurecr.io/simple-chart", + "version": "0.3.0", + }, + }, + Type: "helm.v3", + }, + + // A non-exisitent chart + "simple-chart-2-nonexistent": { + Name: "simple-chart-2", + Properties: Map{ + "chart": Map{ + "repo": "symphonycr.azurecr.io/simple-chart", + "version": "0.3.0-non-existent", + }, + }, + Type: "helm.v3", + }, + + "mongodb-configmap": { + Name: "mongodb", + Type: "yaml.k8s", + Properties: Map{ + "resource": Map{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": Map{ + "name": "mongodb", + }, + "data": Map{ + "database": "mongodb", + "database_uri": "mongodb://localhost:27017", + }, + }, + }, + }, + + "mongodb-configmap-modified": { + Name: "mongodb", + Type: "yaml.k8s", + Properties: Map{ + "resource": Map{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": Map{ + "name": "mongodb", + }, + "data": Map{ + "database": "mongodb", + "database_uri": "mongodb://localhost:27020", // changed port + }, + }, + }, + }, + + "mongodb-constraint": { + Name: "mongodb-constraint", + Type: "yaml.k8s", + Properties: Map{ + "resource": Map{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": Map{ + "name": "mongodb-constraint", + }, + "data": Map{ + "database": "mongodb", + "database_uri": "mongodb://localhost:27017", + }, + }, + }, + Constraints: "${{$equal($property('OS'),'windows')}}", + }, + + "nginx": { + Name: "nginx", + Properties: Map{ + "resource": Map{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": Map{ + "name": "nginx", + }, + "spec": Map{ + "replicas": int64(1), + "selector": Map{ + "matchLabels": Map{ + "app": "nginx", + }, + }, + "template": Map{ + "metadata": Map{ + "labels": Map{ + "app": "nginx", + }, + }, + "spec": Map{ + "containers": []Map{ + { + "image": "nginx:1.21", + "name": "nginx", + "ports": Array{ + Map{"containerPort": int64(80)}, + }, + }, + }, + }, + }, + }, + }, + }, + Type: "yaml.k8s", + }, + + "basic-clusterrole": { + Name: "basic-clusterrole", + Properties: Map{ + "resource": Map{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": Map{ + "name": "basic-clusterrole", + }, + "rules": Array{ + Map{ + "apiGroups": Array{ + "apps", + }, + "resources": Array{ + "deployments", + }, + "verbs": Array{ + "get", + "list", + "watch", + "create", + "update", + }, + }, + }, + }, + }, + Type: "yaml.k8s", + }, + + "basic-configmap-1": { + Name: "basic-configmap-1", + Properties: Map{ + "resource": Map{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": Map{ + "name": "basic-configmap-1", + }, + "data": Map{ + "key": "value", + }, + }, + }, + Type: "yaml.k8s", + }, + + "basic-configmap-1-modified": { + Name: "basic-configmap-1", + Properties: Map{ + "resource": Map{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": Map{ + "name": "basic-configmap-1", + }, + "data": Map{ + "key": "value-modified", + }, + }, + }, + Type: "yaml.k8s", + }, + "basic-configmap-1-params": { + Name: "basic-configmap-1", + Properties: Map{ + "resource": Map{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": Map{ + "name": "basic-configmap-1", + }, + "data": Map{ + "database": "@{{ $param('database')}}", + "database_uri": "@{{ $param('database_uri')}}", + }, + }, + }, + Type: "yaml.k8s", + }, + "basic-configmap-1-params-modified": { + Name: "basic-configmap-1", + Properties: Map{ + "resource": Map{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": Map{ + "name": "basic-configmap-1", + }, + "data": Map{ + "uri": "${{ $param('test')}}", + }, + }, + }, + Type: "yaml.k8s", + }, + "foobar-crd": { + Name: "foobar-crd", + Properties: Map{ + "resource": Map{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": Map{ + "name": "foobars.contoso.io", + }, + "spec": Map{ + "group": "contoso.io", + "version": "v1", + "scope": "Namespaced", + "names": Map{ + "plural": "foobars", + "singular": "foobar", + "kind": "FooBar", + "shortNames": Array{ + "fb", + }, + }, + "versions": Array{ + Map{ + "name": "v1", + "served": true, + "storage": true, + "schema": Map{ + "openAPIV3Schema": Map{ + "type": "object", + "properties": Map{ + "spec": Map{ + "type": "object", + "properties": Map{ + "foo": Map{ + "type": "string", + }, + "bar": Map{ + "type": "string", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "statusProbe": Map{ + "succeededValues": Array{"True"}, + "statusPath": `$.status.conditions[?(@.type == "Established")].status`, + "initialWait": "5s", + }, + }, + Type: "yaml.k8s", + }, + "simple-foobar": { + Name: "simple-foobar", + Properties: Map{ + "resource": Map{ + "apiVersion": "contoso.io/v1", + "kind": "FooBar", + "metadata": Map{ + "name": "simple-foobar", + }, + "spec": Map{ + "foo": "foo", + "bar": "bar", + }, + }, + }, + Type: "yaml.k8s", + Dependencies: []string{ + "foobar-crd", + }, + }, + + // A simple chart with a simple templated expression. + "expressions-1": { + Name: "expressions-1", + Properties: Map{ + "chart": Map{ + "repo": "symphonycr.azurecr.io/simple-chart", + "version": "0.3.0", + }, + "foo": `@{{ $property("color") + ' ' + $property("OS") }}`, + "testGtNumbers": `@{{ $gt("2", 1.0)}}`, + "testGeNumbers": `@{{ $ge(2, "1.0")}}`, + "testLtNumbers": `@{{ $lt("2", 1.0)}}`, + "testLeNumbers": `@{{ $le(2, "1.0")}}`, + "testBetweenNumbers": `@{{ $between(2, "1", 3)}}`, + }, + Type: "helm.v3", + }, + + // A simple chart with an invalid templated expression, $property("will-fail") does not exist on the target. + "expressions-1-failed": { + Name: "expressions-1", + Properties: Map{ + "chart": Map{ + "repo": "symphonycr.azurecr.io/simple-chart", + "version": "0.3.0", + }, + "foo": `${{ $property("will-fail") + ' ' + $property("OS") }}`, + "testGtNumbers": `${{ $gt("2", 1.0)}}`, + "testGeNumbers": `${{ $ge(2, "1.0")}}`, + "testLtNumbers": `${{ $lt("2", 1.0)}}`, + "testLeNumbers": `${{ $le(2, "1.0")}}`, + "testBetweenNumbers": `${{ $between(2, "1", 3)}}`, + }, + Type: "helm.v3", + }, + + // A simple chart with a simple templated expression. + "expressions-1-soln": { + Name: "expressions-1-soln", + Properties: Map{ + "resource": Map{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": Map{ + "name": "expressions-1-soln", + "foo": `@{{ $property("color") + ' ' + $property("OS") }}`, + "normalString": `This is interpreted as a normal string @{{ $property("wont-fail") }}`, + "testEqualNumbers": `@{{ $equal(123, 123) }}`, + "testNotTrue": `@{{ $not("true")}}`, + "testNotNotTrue": `@{{ $not($not(true))}}`, + "testPropertyAnd": `@{{ $and($equal($property("OS"), "windows") , $equal("yes", "no"))}}`, + "testPropertyOr": `@{{ $or($equal($property("OS"), "windows") , $equal("yes", "no"))}}`, + }, + }, + }, + Type: "yaml.k8s", + }, + + // A simple chart with an invalid templated expression, $property("will-fail") does not exist on the target. + "expressions-1-soln-failed": { + Name: "expressions-1-soln", + Properties: Map{ + "chart": Map{ + "repo": "symphonycr.azurecr.io/simple-chart", + "version": "0.3.0", + }, + "name": "expressions-1-soln", + "foo": `@{{ $property("will-fail") + ' ' + $property("OS") }}`, + "normalString": `This is interpreted as a normal string @{{ $property("wont-fail") }}`, + "testEqualNumbers": `@{{ $equal(123, 123) }}`, + "testNotTrue": `@{{ $not("true")}}`, + "testNotNotTrue": `@{{ $not($not(true))}}`, + "testPropertyAnd": `@{{ $and($equal($property("OS"), "windows") , $equal("yes", "no"))}}`, + "testPropertyOr": `@{{ $or($equal($property("OS"), "windows") , $equal("yes", "no"))}}`, + }, + Type: "helm.v3", + }, + + "simple-http": { + Name: "simple-http", + Properties: Map{ + "http.url": "https://learn.microsoft.com/en-us/content-nav/azure.json?", + "http.method": "GET", + }, + Type: "http", + }, + "simple-http-invalid-url": { + Name: "simple-http", + Properties: Map{ + "http.url": "https://learn.microsoft.com/en-us/test/invalid/url", + "http.method": "GET", + }, + Type: "http", + }, + "e4k": { + Name: "e4k", + Properties: map[string]interface{}{ + "chart": map[string]interface{}{ + "repo": "e4kpreview.azurecr.io/helm/az-e4k", + "version": "0.3.0", + }, + }, + Type: "helm.v3", + }, + "e4k-broker": { + Name: "e4k-high-availability-broker", + Properties: map[string]interface{}{ + "chart": map[string]interface{}{ + "repo": "symphonycr.azurecr.io/az-e4k-broker", + "version": "0.1.0", + }, + }, + Type: "helm.v3", + }, + "bluefin-extension": { + Name: "bluefin", + Properties: map[string]interface{}{ + "chart": map[string]interface{}{ + "repo": "azbluefin.azurecr.io/helm/bluefin-arc-extension", + "version": "0.2.0-20230706.3-develop", + }, + }, + Type: "helm.v3", + }, + "bluefin-instance": { + Name: "bluefin-instance", + Properties: map[string]interface{}{ + "resource": map[string]interface{}{ + "apiVersion": "bluefin.az-bluefin.com/v1", + "kind": "Instance", + "metadata": map[string]interface{}{ + "name": "bf-instance", + "namespace": "default", + }, + "spec": map[string]interface{}{ + "displayName": "Test Instance", + "otelCollectorAddress": "otel-collector.alice-springs.svc.cluster.local:4317", + }, + }, + }, + Type: "yaml.k8s", + }, + + "bluefin-pipeline": { + Name: "test-pipeline", + Properties: map[string]interface{}{ + "resource": map[string]interface{}{ + "apiVersion": "bluefin.az-bluefin.com/v1", + "kind": "Pipeline", + "metadata": map[string]interface{}{ + "name": "bf-pipeline", + "namespace": "default", + }, + "spec": map[string]interface{}{ + "displayName": "bf-pipeline", + "enabled": true, + "input": map[string]interface{}{ + "description": "Read from topic Thermostat 3", + "displayName": "E4K", + "format": map[string]interface{}{"type": "json"}, + "mqttConnectionInfo": map[string]interface{}{ + "broker": "tcp://azedge-dmqtt-frontend:1883", + "password": "password", + "username": "client1", + }, + "next": []interface{}{"node-22f2"}, + "topics": []interface{}{ + map[string]interface{}{ + "name": "alice-springs/data/opc-ua-connector/opc-ua-connector/thermostat-sample-3", + }, + }, + "type": "input/mqtt@v1", + "viewOptions": map[string]interface{}{ + "position": map[string]interface{}{ + "x": 0, + "y": 80, + }, + }, + }, + "partitionCount": 6, + "stages": map[string]interface{}{ + "node-22f2": map[string]interface{}{ + "displayName": "No-op", + "next": []interface{}{"output"}, + "query": ".", + "type": "processor/transform@v1", + "viewOptions": map[string]interface{}{ + "position": map[string]interface{}{ + "x": 0, + "y": 208, + }, + }, + }, + "output": map[string]interface{}{ + "broker": "tcp://azedge-dmqtt-frontend:1883", + "description": "Publish to topic demo-output-topic", + "displayName": "E4K", + "format": map[string]interface{}{"type": "json"}, + "password": "password", + "timeout": "45ms", + "topic": "alice-springs/data/demo-output", + "type": "output/mqtt@v1", + "username": "client1", + "viewOptions": map[string]interface{}{ + "position": map[string]interface{}{ + "x": 0, + "y": 336, + }, + }, + }, + }, + }, + }, + }, + Type: "yaml.k8s", + }, + } +) diff --git a/test/integration/lib/testhelpers/helmvalues.go b/test/integration/lib/testhelpers/helmvalues.go new file mode 100644 index 000000000..059035a67 --- /dev/null +++ b/test/integration/lib/testhelpers/helmvalues.go @@ -0,0 +1,49 @@ +package testhelpers + +import ( + "fmt" + "reflect" + "strconv" + "strings" +) + +type ( + HelmValues map[string]interface{} +) + +func (h HelmValues) String() string { + flatMap := make(map[string]interface{}, len(h)) + flatten(reflect.ValueOf(h), "", flatMap) + + items := make([]string, 0, len(flatMap)) + for key, value := range flatMap { + switch v := value.(type) { + case string: + value = fmt.Sprintf(`"%s"`, strings.ReplaceAll(v, `"`, `\"`)) + } + items = append(items, fmt.Sprintf("--set %s=%v", key, value)) + } + return strings.Join(items, " ") +} + +func flatten(inputValue reflect.Value, parentKey string, flatMap map[string]interface{}) { + if inputValue.Kind() == reflect.Interface || inputValue.Kind() == reflect.Ptr { + inputValue = inputValue.Elem() + } + inputType := inputValue.Type() + + switch inputType.Kind() { + case reflect.Map: + for _, key := range inputValue.MapKeys() { + flatten(inputValue.MapIndex(key), parentKey+"."+key.String(), flatMap) + } + case reflect.Slice: + for i := 0; i < inputValue.Len(); i++ { + flatten(inputValue.Index(i), parentKey+"["+strconv.Itoa(i)+"]", flatMap) + } + default: + parentKey = strings.TrimPrefix(parentKey, ".") + inputValue.Type() + flatMap[parentKey] = inputValue.Interface() + } +} diff --git a/test/integration/lib/testhelpers/helpers.go b/test/integration/lib/testhelpers/helpers.go new file mode 100644 index 000000000..73a39bfbf --- /dev/null +++ b/test/integration/lib/testhelpers/helpers.go @@ -0,0 +1,25 @@ +package testhelpers + +import ( + "context" + + "github.com/eclipse-symphony/symphony/test/integration/lib/shell" +) + +func DumpClusterState(ctx context.Context) { + shell.Exec(ctx, "kubectl get all -A -o wide") + shell.Exec(ctx, "kubectl get events -A --sort-by=.metadata.creationTimestamp") + shell.Exec(ctx, "kubectl get targets.fabric.symphony -A -o yaml") + shell.Exec(ctx, "kubectl get solutions.solution.symphony -A -o yaml") + shell.Exec(ctx, "kubectl get instances.solution.symphony -A -o yaml") + shell.Exec(ctx, "helm list -A -o yaml") +} + +func CleanupManifests(ctx context.Context) error { + return shell.ExecAll( + ctx, + "kubectl delete instances.solution.symphony --all -A", + "kubectl delete targets.fabric.symphony --all -A", + "kubectl delete solutions.solution.symphony --all -A", + ) +} diff --git a/test/integration/lib/testhelpers/manifestbuilder.go b/test/integration/lib/testhelpers/manifestbuilder.go index dda66219e..25b6053d5 100644 --- a/test/integration/lib/testhelpers/manifestbuilder.go +++ b/test/integration/lib/testhelpers/manifestbuilder.go @@ -9,191 +9,10 @@ package testhelpers import ( "fmt" "os" + "regexp" "gopkg.in/yaml.v2" -) - -type ( - Metadata struct { - Annotations map[string]string `yaml:"annotations,omitempty"` - Name string `yaml:"name,omitempty"` - } - - // Solution describes the structure of symphony solution yaml file - Solution struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - Metadata Metadata `yaml:"metadata"` - Spec SolutionSpec `yaml:"spec"` - } - - SolutionSpec struct { - DisplayName string `yaml:"displayName,omitempty"` - Metadata map[string]string `yaml:"metadata,omitempty"` - Components []ComponentSpec `yaml:"components,omitempty"` - } - - // Target describes the structure of symphony target yaml file - Target struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - Metadata Metadata `yaml:"metadata"` - Spec TargetSpec `yaml:"spec"` - } - - TargetSpec struct { - DisplayName string `yaml:"displayName"` - Scope string `yaml:"scope,omitempty"` - Components []ComponentSpec `yaml:"components,omitempty"` - Topologies []Topology `yaml:"topologies"` - } - - Topology struct { - Bindings []Binding `yaml:"bindings"` - } - - Binding struct { - Config Config `yaml:"config"` - Provider string `yaml:"provider"` - Role string `yaml:"role"` - } - - Config struct { - InCluster string `yaml:"inCluster"` - } - - ComponentSpec struct { - Name string `yaml:"name"` - Properties map[string]interface{} `yaml:"properties"` - Type string `yaml:"type"` - } -) - -var ( - ComponetsMap = map[string]ComponentSpec{ - "e4k": { - Name: "e4k", - Properties: map[string]interface{}{ - "chart": map[string]interface{}{ - "repo": "e4kpreview.azurecr.io/helm/az-e4k", - "version": "0.3.0", - }, - }, - Type: "helm.v3", - }, - "e4k-broker": { - Name: "e4k-high-availability-broker", - Properties: map[string]interface{}{ - "chart": map[string]interface{}{ - "repo": "symphonycr.azurecr.io/az-e4k-broker", - "version": "0.1.0", - }, - }, - Type: "helm.v3", - }, - "bluefin-extension": { - Name: "bluefin", - Properties: map[string]interface{}{ - "chart": map[string]interface{}{ - "repo": "azbluefin.azurecr.io/helm/bluefin-arc-extension", - "version": "0.2.0-20230706.3-develop", - }, - }, - Type: "helm.v3", - }, - "bluefin-instance": { - Name: "bluefin-instance", - Properties: map[string]interface{}{ - "resource": map[string]interface{}{ - "apiVersion": "bluefin.az-bluefin.com/v1", - "kind": "Instance", - "metadata": map[string]interface{}{ - "name": "bf-instance", - "namespace": "default", - }, - "spec": map[string]interface{}{ - "displayName": "Test Instance", - "otelCollectorAddress": "otel-collector.alice-springs.svc.cluster.local:4317", - }, - }, - }, - Type: "yaml.k8s", - }, - - "bluefin-pipeline": { - Name: "test-pipeline", - Properties: map[string]interface{}{ - "resource": map[string]interface{}{ - "apiVersion": "bluefin.az-bluefin.com/v1", - "kind": "Pipeline", - "metadata": map[string]interface{}{ - "name": "bf-pipeline", - "namespace": "default", - }, - "spec": map[string]interface{}{ - "displayName": "bf-pipeline", - "enabled": true, - "input": map[string]interface{}{ - "description": "Read from topic Thermostat 3", - "displayName": "E4K", - "format": map[string]interface{}{"type": "json"}, - "mqttConnectionInfo": map[string]interface{}{ - "broker": "tcp://azedge-dmqtt-frontend:1883", - "password": "password", - "username": "client1", - }, - "next": []interface{}{"node-22f2"}, - "topics": []interface{}{ - map[string]interface{}{ - "name": "alice-springs/data/opc-ua-connector/opc-ua-connector/thermostat-sample-3", - }, - }, - "type": "input/mqtt@v1", - "viewOptions": map[string]interface{}{ - "position": map[string]interface{}{ - "x": 0, - "y": 80, - }, - }, - }, - "partitionCount": 6, - "stages": map[string]interface{}{ - "node-22f2": map[string]interface{}{ - "displayName": "No-op", - "next": []interface{}{"output"}, - "query": ".", - "type": "processor/transform@v1", - "viewOptions": map[string]interface{}{ - "position": map[string]interface{}{ - "x": 0, - "y": 208, - }, - }, - }, - "output": map[string]interface{}{ - "broker": "tcp://azedge-dmqtt-frontend:1883", - "description": "Publish to topic demo-output-topic", - "displayName": "E4K", - "format": map[string]interface{}{"type": "json"}, - "password": "password", - "timeout": "45ms", - "topic": "alice-springs/data/demo-output", - "type": "output/mqtt@v1", - "username": "client1", - "viewOptions": map[string]interface{}{ - "position": map[string]interface{}{ - "x": 0, - "y": 336, - }, - }, - }, - }, - }, - }, - }, - Type: "yaml.k8s", - }, - } + "k8s.io/apimachinery/pkg/util/uuid" ) // BuildManifestFile modifies the target/solution manifest files @@ -283,3 +102,159 @@ func addComponentsToTarget(data []byte, components []string) (Target, error) { return target, nil } + +type ( + InstanceOptions struct { + NamePostfix string + Scope string + Namespace string + Parameters map[string]interface{} + PostProcess func(*Instance) + Solution string + } + + SolutionOptions struct { + NamePostfix string + ComponentNames []string + Namespace string + PostProcess func(*Solution) + SolutionName string + } + + TargetOptions = struct { + NamePostfix string + Scope string + Namespace string + ComponentNames []string + Properties map[string]string + PostProcess func(*Target) + } +) + +const ( + AzureOperationIdKey = "management.azure.com/operationId" +) + +var leadingDash = regexp.MustCompile(`^-`) + +func PatchSolution(data []byte, opts SolutionOptions) ([]byte, error) { + var solution Solution + err := yaml.Unmarshal(data, &solution) + if err != nil { + return nil, err + } + yamlComponents := make([]ComponentSpec, 0) + for _, name := range opts.ComponentNames { + if val, ok := ComponetsMap[name]; ok { + yamlComponents = append(yamlComponents, val) + } else { + return nil, fmt.Errorf("component %s not found", name) + } + } + + if solution.Metadata.Annotations == nil { + solution.Metadata.Annotations = make(map[string]string) + } + + if opts.NamePostfix != "" { + solution.Metadata.Name = fmt.Sprintf("%s-%s", solution.Metadata.Name, opts.NamePostfix) + solution.Metadata.Name = leadingDash.ReplaceAllString(solution.Metadata.Name, "") + } + + if opts.Namespace != "" { + solution.Metadata.Namespace = opts.Namespace + } + + if opts.SolutionName != "" { + solution.Metadata.Name = opts.SolutionName + } + + solution.Metadata.Annotations[AzureOperationIdKey] = string(uuid.NewUUID()) + solution.Spec.Components = yamlComponents + if opts.PostProcess != nil { + opts.PostProcess(&solution) + } + return yaml.Marshal(solution) +} + +func PatchTarget(data []byte, opts TargetOptions) ([]byte, error) { + var target Target + err := yaml.Unmarshal(data, &target) + if err != nil { + return nil, err + } + + for _, name := range opts.ComponentNames { + if val, ok := ComponetsMap[name]; ok { + target.Spec.Components = append(target.Spec.Components, val) + } else { + return nil, fmt.Errorf("component %s not found", name) + } + } + if opts.NamePostfix != "" { + target.Metadata.Name = fmt.Sprintf("%s-%s", target.Metadata.Name, opts.NamePostfix) + target.Metadata.Name = leadingDash.ReplaceAllString(target.Metadata.Name, "") + } + + if opts.Namespace != "" { + target.Metadata.Namespace = opts.Namespace + } + + if opts.Scope != "" { + target.Spec.Scope = opts.Scope + } + + if target.Metadata.Annotations == nil { + target.Metadata.Annotations = make(map[string]string) + } + + if opts.Properties != nil { + target.Spec.Properties = opts.Properties + } + + target.Metadata.Annotations[AzureOperationIdKey] = string(uuid.NewUUID()) + if opts.PostProcess != nil { + opts.PostProcess(&target) + } + + return yaml.Marshal(target) +} + +func PatchInstance(data []byte, opts InstanceOptions) ([]byte, error) { + var instance Instance + err := yaml.Unmarshal(data, &instance) + if err != nil { + return nil, err + } + + if opts.NamePostfix != "" { + instance.Metadata.Name = fmt.Sprintf("%s-%s", instance.Metadata.Name, opts.NamePostfix) + instance.Metadata.Name = leadingDash.ReplaceAllString(instance.Metadata.Name, "") + } + + if opts.Namespace != "" { + instance.Metadata.Namespace = opts.Namespace + } + + if opts.Scope != "" { + instance.Spec.Scope = opts.Scope + } + + if opts.Solution != "" { + instance.Spec.Solution = opts.Solution + } + + if opts.Parameters != nil { + instance.Spec.Parameters = opts.Parameters + } + + if instance.Metadata.Annotations == nil { + instance.Metadata.Annotations = make(map[string]string) + } + + instance.Metadata.Annotations[AzureOperationIdKey] = string(uuid.NewUUID()) + if opts.PostProcess != nil { + opts.PostProcess(&instance) + } + return yaml.Marshal(instance) +} diff --git a/test/integration/lib/testhelpers/types.go b/test/integration/lib/testhelpers/types.go new file mode 100644 index 000000000..adfa0bbb2 --- /dev/null +++ b/test/integration/lib/testhelpers/types.go @@ -0,0 +1,89 @@ +package testhelpers + +// TODO: Switch over to symphony core types from the /k8s/api folder +type ( + Metadata struct { + Annotations map[string]string `yaml:"annotations,omitempty"` + Name string `yaml:"name,omitempty"` + Namespace string `yaml:"namespace,omitempty"` + } + + // Solution describes the structure of symphony solution yaml file + Solution struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata Metadata `yaml:"metadata"` + Spec SolutionSpec `yaml:"spec"` + } + + SolutionSpec struct { + DisplayName string `yaml:"displayName,omitempty"` + Scope string `yaml:"scope,omitempty"` + Metadata map[string]string `yaml:"metadata,omitempty"` + Components []ComponentSpec `yaml:"components,omitempty"` + } + + // Target describes the structure of symphony target yaml file + Target struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata Metadata `yaml:"metadata"` + Spec TargetSpec `yaml:"spec"` + } + + TargetSpec struct { + DisplayName string `yaml:"displayName"` + Scope string `yaml:"scope"` + Components []ComponentSpec `yaml:"components,omitempty"` + Topologies []Topology `yaml:"topologies"` + Properties map[string]string `yaml:"properties,omitempty"` + } + + Topology struct { + Bindings []Binding `yaml:"bindings"` + } + + Binding struct { + Config Config `yaml:"config"` + Provider string `yaml:"provider"` + Role string `yaml:"role"` + } + + Config struct { + InCluster string `yaml:"inCluster"` + } + + ComponentSpec struct { + Name string `yaml:"name"` + Parameters map[string]ParameterDefinition `yaml:"parameters,omitempty"` + Properties map[string]interface{} `yaml:"properties"` + Type string `yaml:"type"` + Constraints string `yaml:"constraints,omitempty"` + Dependencies []string `yaml:"dependencies,omitempty"` + } + + Instance struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata Metadata `yaml:"metadata"` + Spec InstanceSpec `yaml:"spec"` + } + + InstanceSpec struct { + DisplayName string `yaml:"displayName"` + Target TargetSelector `yaml:"target"` + Solution string `yaml:"solution"` + Scope string `yaml:"scope"` + Parameters map[string]interface{} `yaml:"parameters,omitempty"` + } + + TargetSelector struct { + Name string `yaml:"name,omitempty"` + Selector map[string]string `yaml:"selector,omitempty"` + } + + ParameterDefinition struct { + Type string `yaml:"type"` + DefaultValue interface{} `yaml:"default"` + } +) diff --git a/test/integration/magefile.go b/test/integration/magefile.go index 29eab13a3..0af83b8b1 100644 --- a/test/integration/magefile.go +++ b/test/integration/magefile.go @@ -93,7 +93,11 @@ func listTests(dir string) ([]string, error) { // Read test subfolders for _, entry := range subDirs { if entry.IsDir() { - results = append(results, filepath.Join(dir, entry.Name())) + dirPath := filepath.Join(dir, entry.Name()) + filePath := filepath.Join(dirPath, "magefile.go") + if _, err := os.Stat(filePath); err == nil { + results = append(results, dirPath) + } } } diff --git a/test/integration/scenarios/04.workflow/magefile.go b/test/integration/scenarios/04.workflow/magefile.go index 7add4985e..dfc8d8b71 100644 --- a/test/integration/scenarios/04.workflow/magefile.go +++ b/test/integration/scenarios/04.workflow/magefile.go @@ -22,7 +22,7 @@ import ( // Test config const ( TEST_NAME = "workflow test" - TEST_TIMEOUT = "3m" + TEST_TIMEOUT = "4m" ) var ( diff --git a/test/integration/scenarios/04.workflow/manifest/instance-catalog.yaml b/test/integration/scenarios/04.workflow/manifest/instance-catalog.yaml index b0f42a19f..aa157d3c8 100644 --- a/test/integration/scenarios/04.workflow/manifest/instance-catalog.yaml +++ b/test/integration/scenarios/04.workflow/manifest/instance-catalog.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: site-instance -spec: - siteId: hq +spec: type: instance properties: spec: diff --git a/test/integration/scenarios/05.catalog/catalogs/asset.yaml b/test/integration/scenarios/05.catalog/catalogs/asset.yaml index 0c35d65bb..5d2135fc9 100644 --- a/test/integration/scenarios/05.catalog/catalogs/asset.yaml +++ b/test/integration/scenarios/05.catalog/catalogs/asset.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: asset -spec: - siteId: hq +spec: type: asset properties: name: "東京" diff --git a/test/integration/scenarios/05.catalog/catalogs/config.yaml b/test/integration/scenarios/05.catalog/catalogs/config.yaml index 88b2899b8..6a5231ddf 100644 --- a/test/integration/scenarios/05.catalog/catalogs/config.yaml +++ b/test/integration/scenarios/05.catalog/catalogs/config.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: config -spec: - siteId: hq +spec: type: config metadata: schema: schema diff --git a/test/integration/scenarios/05.catalog/catalogs/instance.yaml b/test/integration/scenarios/05.catalog/catalogs/instance.yaml index 69f942adb..d77bd9ea0 100644 --- a/test/integration/scenarios/05.catalog/catalogs/instance.yaml +++ b/test/integration/scenarios/05.catalog/catalogs/instance.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: instance -spec: - siteId: hq +spec: type: instance properties: spec: diff --git a/test/integration/scenarios/05.catalog/catalogs/schema.yaml b/test/integration/scenarios/05.catalog/catalogs/schema.yaml index dbcb85085..c3f2faedb 100644 --- a/test/integration/scenarios/05.catalog/catalogs/schema.yaml +++ b/test/integration/scenarios/05.catalog/catalogs/schema.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: schema -spec: - siteId: hq +spec: type: schema properties: spec: diff --git a/test/integration/scenarios/05.catalog/catalogs/solution.yaml b/test/integration/scenarios/05.catalog/catalogs/solution.yaml index a19bb7350..bf04bb044 100644 --- a/test/integration/scenarios/05.catalog/catalogs/solution.yaml +++ b/test/integration/scenarios/05.catalog/catalogs/solution.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: solution -spec: - siteId: hq +spec: type: solution properties: spec: diff --git a/test/integration/scenarios/05.catalog/catalogs/target.yaml b/test/integration/scenarios/05.catalog/catalogs/target.yaml index a16994ef7..c7e42f8b6 100644 --- a/test/integration/scenarios/05.catalog/catalogs/target.yaml +++ b/test/integration/scenarios/05.catalog/catalogs/target.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: target -spec: - siteId: hq +spec: type: target properties: spec: diff --git a/test/integration/scenarios/05.catalog/catalogs/wrongconfig.yaml b/test/integration/scenarios/05.catalog/catalogs/wrongconfig.yaml index 3e3024d76..4a9ea9185 100644 --- a/test/integration/scenarios/05.catalog/catalogs/wrongconfig.yaml +++ b/test/integration/scenarios/05.catalog/catalogs/wrongconfig.yaml @@ -2,8 +2,7 @@ apiVersion: federation.symphony/v1 kind: Catalog metadata: name: wrongconfig -spec: - siteId: hq +spec: type: config metadata: schema: schema diff --git a/test/integration/scenarios/06.ado/create_update_fallback_test.go b/test/integration/scenarios/06.ado/create_update_fallback_test.go new file mode 100644 index 000000000..8b837822e --- /dev/null +++ b/test/integration/scenarios/06.ado/create_update_fallback_test.go @@ -0,0 +1,147 @@ +package scenarios_test + +import ( + "context" + _ "embed" + + "github.com/eclipse-symphony/symphony/packages/testutils/conditions" + "github.com/eclipse-symphony/symphony/packages/testutils/expectations" + "github.com/eclipse-symphony/symphony/packages/testutils/expectations/kube" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/eclipse-symphony/symphony/test/integration/lib/shell" + "github.com/eclipse-symphony/symphony/test/integration/lib/testhelpers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Create/update resources for rollback testing", Ordered, func() { + type TestCase struct { + TargetComponents []string + SolutionComponents []string + SolutionComponentsV2 []string + PostUpdateExpectation types.Expectation + PostRevertExpectation types.Expectation + TargetProperties map[string]string + } + var instanceBytes []byte + var targetBytes []byte + var solutionBytes []byte + var solutionBytesV2 []byte + var targetProps map[string]string + + BeforeAll(func(ctx context.Context) { + By("installing orchestrator in the cluster") + shell.LocalenvCmd(ctx, "mage cluster:deploy") + + By("setting the default testing lib logger") + logger.SetDefaultLogger(GinkgoWriter.Printf) + }) + + AfterAll(func() { + By("uninstalling orchestrator from the cluster") + err := shell.LocalenvCmd(context.Background(), "mage destroy all") + Expect(err).ToNot(HaveOccurred()) + }) + + JustAfterEach(func(ctx context.Context) { + if CurrentSpecReport().Failed() { + By("dumping cluster state") + testhelpers.DumpClusterState(ctx) + } + }) + + runner := func(ctx context.Context, testcase TestCase) { + By("setting the components for the target") + var err error + + props := targetProps + if testcase.TargetProperties != nil { + props = testcase.TargetProperties + } + // Patch the target manifest with the target options + targetBytes, err = testhelpers.PatchTarget(defaultTargetManifest, testhelpers.TargetOptions{ + ComponentNames: testcase.TargetComponents, + Properties: props, + }) + Expect(err).ToNot(HaveOccurred()) + + By("setting the components for Solution V1") + solutionBytes, err = testhelpers.PatchSolution(defaultSolutionManifest, testhelpers.SolutionOptions{ + ComponentNames: testcase.SolutionComponents, + }) + Expect(err).ToNot(HaveOccurred()) + + By("preparing the instance bytes with a new operation id for Solution V1") + instanceBytes, err = testhelpers.PatchInstance(defaultInstanceManifest, testhelpers.InstanceOptions{}) + Expect(err).ToNot(HaveOccurred()) + + By("deploying the Target") + err = shell.PipeInExec(ctx, "kubectl apply -f -", targetBytes) + Expect(err).ToNot(HaveOccurred()) + + By("deploying Solution V1") + err = shell.PipeInExec(ctx, "kubectl apply -f -", solutionBytes) + Expect(err).ToNot(HaveOccurred()) + + By("deploying the Instance that references Solution V1") + err = shell.PipeInExec(ctx, "kubectl apply -f -", instanceBytes) + Expect(err).ToNot(HaveOccurred()) + + By("setting the components for Solution V2, an invalid solution") + solutionBytesV2, err = testhelpers.PatchSolution(defaultSolutionManifest, testhelpers.SolutionOptions{ + ComponentNames: testcase.SolutionComponentsV2, + SolutionName: "solution-v2", + }) + Expect(err).ToNot(HaveOccurred()) + + By("deploying Solution V2") + err = shell.PipeInExec(ctx, "kubectl apply -f -", solutionBytesV2) + Expect(err).ToNot(HaveOccurred()) + + By("preparing the instance bytes with a new operation id for Solution V2") + instanceBytes, err = testhelpers.PatchInstance(defaultInstanceManifest, testhelpers.InstanceOptions{ + Solution: "solution-v2", + }) + Expect(err).ToNot(HaveOccurred()) + + By("updating the Instance to use Solution V2") + err = shell.PipeInExec(ctx, "kubectl apply -f -", instanceBytes) + Expect(err).ToNot(HaveOccurred()) + + By("verifying deployment of Instance referencing Solution V2 fails") + err = testcase.PostUpdateExpectation.Verify(ctx) + Expect(err).ToNot(HaveOccurred()) + + By("reverting the Instance to use Solution V1") + instanceBytes, err = testhelpers.PatchInstance(defaultInstanceManifest, testhelpers.InstanceOptions{ + Solution: "solution", + }) + Expect(err).ToNot(HaveOccurred()) + + By("Deploying the Instance to use Solution V1 again") + err = shell.PipeInExec(ctx, "kubectl apply -f -", instanceBytes) + Expect(err).ToNot(HaveOccurred()) + + By("verifying deployment of Instance referencing Solution V1 succeeds") + err = testcase.PostRevertExpectation.Verify(ctx) + Expect(err).ToNot(HaveOccurred()) + } + + DescribeTable("fail to deploy solution v2 then rollback to v1", Ordered, runner, + Entry("with a single component", TestCase{ + TargetComponents: []string{"simple-chart-1"}, + SolutionComponents: []string{"simple-chart-2"}, + SolutionComponentsV2: []string{"simple-chart-2-nonexistent"}, + PostUpdateExpectation: expectations.All( + kube.Must(kube.Instance("instance", "default", kube.WithCondition(conditions.All( // make sure the instance named 'instance' is present in the 'default' namespace + kube.ProvisioningFailedCondition, // and it is failed + //jq.Equality(".status.provisioningStatus.error.details[0].details[0].code", "Update Failed"), + )))), + ), + PostRevertExpectation: expectations.All( + successfullInstanceExpectation, + ), + }), + ) +}) diff --git a/test/integration/scenarios/06.ado/create_update_test.go b/test/integration/scenarios/06.ado/create_update_test.go new file mode 100644 index 000000000..5bfcefa4b --- /dev/null +++ b/test/integration/scenarios/06.ado/create_update_test.go @@ -0,0 +1,540 @@ +package scenarios_test + +import ( + "context" + _ "embed" + "time" + + "github.com/eclipse-symphony/symphony/packages/testutils/conditions" + "github.com/eclipse-symphony/symphony/packages/testutils/conditions/jq" + "github.com/eclipse-symphony/symphony/packages/testutils/expectations" + "github.com/eclipse-symphony/symphony/packages/testutils/expectations/helm" + "github.com/eclipse-symphony/symphony/packages/testutils/expectations/kube" + "github.com/eclipse-symphony/symphony/packages/testutils/helpers" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/eclipse-symphony/symphony/test/integration/lib/shell" + "github.com/eclipse-symphony/symphony/test/integration/lib/testhelpers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Create resources with sequential changes", Ordered, func() { + type TestCase struct { + TargetComponents []string + SolutionComponents []string + Expectation types.Expectation + TargetProperties map[string]string + InstanceParameters map[string]interface{} + } + var instanceBytes []byte + var targetBytes []byte + var solutionBytes []byte + var specTimeout = 120 * time.Second + var targetProps map[string]string + var instanceParams map[string]interface{} + + BeforeAll(func(ctx context.Context) { + By("installing orchestrator in the cluster") + shell.LocalenvCmd(ctx, "mage cluster:deploy") + + By("setting the default testing lib logger") + logger.SetDefaultLogger(GinkgoWriter.Printf) + }) + + AfterAll(func() { + By("uninstalling orchestrator from the cluster") + err := shell.LocalenvCmd(context.Background(), "mage destroy all") + Expect(err).ToNot(HaveOccurred()) + }) + + JustAfterEach(func(ctx context.Context) { + if CurrentSpecReport().Failed() { + By("dumping cluster state") + testhelpers.DumpClusterState(ctx) + } + }) + + runner := func(ctx context.Context, testcase TestCase) { + By("setting the components for the target") + var err error + props := targetProps + params := instanceParams + if testcase.TargetProperties != nil { + props = testcase.TargetProperties + } + + if testcase.InstanceParameters != nil { + params = testcase.InstanceParameters + } + targetBytes, err = testhelpers.PatchTarget(defaultTargetManifest, testhelpers.TargetOptions{ + ComponentNames: testcase.TargetComponents, + Properties: props, + }) + Expect(err).ToNot(HaveOccurred()) + + By("setting the components for the solution") + solutionBytes, err = testhelpers.PatchSolution(defaultSolutionManifest, testhelpers.SolutionOptions{ + ComponentNames: testcase.SolutionComponents, + }) + Expect(err).ToNot(HaveOccurred()) + + By("preparing the instance bytes with a new operation id for the test") + instanceBytes, err = testhelpers.PatchInstance(defaultInstanceManifest, testhelpers.InstanceOptions{ + Parameters: params, + }) + Expect(err).ToNot(HaveOccurred()) + + By("deploying the instance") + err = shell.PipeInExec(ctx, "kubectl apply -f -", instanceBytes) + Expect(err).ToNot(HaveOccurred()) + + By("deploying the target") + err = shell.PipeInExec(ctx, "kubectl apply -f -", targetBytes) + Expect(err).ToNot(HaveOccurred()) + + By("deploying the solution") + err = shell.PipeInExec(ctx, "kubectl apply -f -", solutionBytes) + Expect(err).ToNot(HaveOccurred()) + + err = testcase.Expectation.Verify(ctx) + Expect(err).ToNot(HaveOccurred()) + } + + DescribeTable("when performing create/update operations", Ordered, runner, + + Entry( + "it should deploy empty target and solution", SpecTimeout(specTimeout), + TestCase{ + Expectation: expectations.All( + successfullInstanceExpectation, + successfullTargetExpectation, + ), + }, + ), + + Entry( + "it should update the target with a simple helm chart", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"simple-chart-1"}, + SolutionComponents: []string{}, + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + ////kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-1", "Updated"), // and the target component 'simple-chart-1' status is updated. OSS has no provisioning status yet + )))), + successfullInstanceExpectation, + helm.MustNew("simple-chart-1", "azure-iot-operations", helm.WithReleaseCondition(helm.DeployedCondition)), // make sure the release is successfully deployed + ), + }, + ), + + Entry( + "it should deploy another simple helm chart in the solution so there are 2 helm releases", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"simple-chart-1"}, // (same as previous entry) + SolutionComponents: []string{"simple-chart-2"}, + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + ////kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-1", nil), // Because nothing changed, the output should be nil + )))), + kube.Must(kube.Instance("instance", "default", kube.WithCondition(conditions.All( // make sure the instance named 'instance' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + ////kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-2", "Updated"), // and the solution component 'simple-chart-2' is created + )))), + + helm.MustNew("simple-chart-.*", "azure-iot-operations", // releases beginning with 'simple-chart-' in the 'azure-iot-operations' namespace + helm.WithReleaseListCondition(conditions.Count(2)), // there should be only 2 releases present + helm.WithReleaseCondition(helm.DeployedCondition), // all releases should have 'deployed' status + ), + helm.MustNew("simple-chart-1", "azure-iot-operations"), + helm.MustNew("simple-chart-2", "azure-iot-operations"), + ), + }, + ), + + Entry( + "it should add a kubernetes config map in the solution", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"simple-chart-1"}, // (same as previous entry) + SolutionComponents: []string{"simple-chart-2", "basic-configmap-1"}, // + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + ////kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-1", nil), // Because the component didn't change + )))), + kube.Must(kube.Instance("instance", "default", kube.WithCondition(conditions.All( // make sure the instance named 'instance' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + ////kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-2", nil), // Because the component didn't change + //kube.ProvisioningStatusComponentOutput("target.basic-configmap-1", "Updated"), // and the solution component 'basic-configmap-1' is created + )))), + helm.MustNew("simple-chart-.*", "azure-iot-operations", // releases beginning with 'simple-chart-' in the 'azure-iot-operations' namespace + helm.WithReleaseListCondition(conditions.Count(2)), // there should be only 2 releases present + helm.WithReleaseCondition(helm.DeployedCondition), // all releases should have 'deployed' status + ), + kube.Must(kube.Resource("basic-configmap-1", "azure-iot-operations", helpers.ConfigMapGVK)), + ), + }, + ), + + Entry( + "it should add a kubernetes clusterrole in the target", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"simple-chart-1", "basic-clusterrole"}, // + SolutionComponents: []string{"simple-chart-2", "basic-configmap-1"}, // (same as previous entry) + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + ////kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-1", nil), // Because the component didn't change + //kube.ProvisioningStatusComponentOutput("target.basic-clusterrole", "Updated"), // and the target component 'basic-clusterrole' is created + )))), + kube.Must(kube.Instance("instance", "default", kube.WithCondition(conditions.All( // make sure the instance named 'instance' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + ////kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-2", nil), // Because the component didn't change + //kube.ProvisioningStatusComponentOutput("target.basic-configmap-1", nil), // Because the component didn't change + )))), + helm.MustNew("simple-chart-.*", "azure-iot-operations", // releases beginning with 'simple-chart-' in the 'azure-iot-operations' namespace + helm.WithReleaseListCondition(conditions.Count(2)), + helm.WithReleaseCondition(helm.DeployedCondition), + ), + kube.Must(kube.Resource("basic-clusterrole", "azure-iot-operations", helpers.ClusterRoleGVK)), + kube.Must(kube.Resource("basic-configmap-1", "azure-iot-operations", helpers.ConfigMapGVK)), + ), + }, + ), + + Entry( + "it should should just update the operation id when a no-op change is made", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"simple-chart-1", "basic-clusterrole"}, // (same as previous entry) + SolutionComponents: []string{"simple-chart-2", "basic-configmap-1"}, // (same as previous entry) + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + ////kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-1", nil), // Because the component didn't change + //kube.ProvisioningStatusComponentOutput("target.basic-clusterrole", nil), // Because the component didn't change + )))), + kube.Must(kube.Instance("instance", "default", kube.WithCondition(conditions.All( // make sure the instance named 'instance' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + ////kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-2", nil), // Because the component didn't change + //kube.ProvisioningStatusComponentOutput("target.basic-configmap-1", nil), // Because the component didn't change + )))), + helm.MustNew("simple-chart-.*", "azure-iot-operations", // releases beginning with 'simple-chart-' in the 'azure-iot-operations' namespace + helm.WithReleaseListCondition(conditions.Count(2)), // there should be only 2 releases present + helm.WithReleaseCondition(helm.DeployedCondition), // all releases should have 'deployed' status + ), + kube.Must(kube.Resource("basic-clusterrole", "azure-iot-operations", helpers.ClusterRoleGVK)), + kube.Must(kube.Resource("basic-configmap-1", "azure-iot-operations", helpers.GVK("", "v1", "ConfigMap"))), + ), + }, + ), + + Entry( + "It should update remove the clusterrole from the target", + TestCase{ + TargetComponents: []string{"simple-chart-1"}, + SolutionComponents: []string{"simple-chart-2", "basic-configmap-1"}, // (same as previous entry) + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + ////kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-1", nil), // Because the component didn't change + //kube.ProvisioningStatusComponentOutput("target.basic-clusterrole", "Deleted"), // and the target component 'basic-clusterrole' is deleted + )))), + kube.Must(kube.Instance("instance", "default", kube.WithCondition(conditions.All( // make sure the instance named 'instance' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + //kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-2", nil), // Because the component didn't change + //kube.ProvisioningStatusComponentOutput("target.basic-configmap-1", nil), // Because the component didn't change + )))), + helm.MustNew("simple-chart-.*", "azure-iot-operations", // releases beginning with 'simple-chart-' in the 'azure-iot-operations' namespace + helm.WithReleaseListCondition(conditions.Count(2)), // there should be only 2 releases present + helm.WithReleaseCondition(helm.DeployedCondition), // all releases should have 'deployed' status + ), + kube.Must(kube.Resource("basic-configmap-1", "azure-iot-operations", helpers.GVK("", "v1", "ConfigMap"))), + kube.Must(kube.AbsentResource("basic-clusterrole", "azure-iot-operations", helpers.ClusterRoleGVK)), + ), + }, + ), + + Entry( + "It should update remove the simple-helmchart-2 from the solution", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"simple-chart-1"}, // (same as previous entry) + SolutionComponents: []string{"basic-configmap-1"}, + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + //kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-1", nil), // Because the component didn't change + //kube.ProvisioningStatusComponentOutput("target.basic-clusterrole", nil), // Because it was deleted in the previous reconciliation + )))), + kube.Must(kube.Instance("instance", "default", kube.WithCondition(conditions.All( // make sure the instance named 'instance' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + //kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-2", "Deleted"), // and the solution component 'simple-chart-2' is deleted + //kube.ProvisioningStatusComponentOutput("target.basic-configmap-1", nil), // Because the component didn't change + )))), + helm.MustNew("simple-chart-.*", "azure-iot-operations", // releases beginning with 'simple-chart-' in the 'azure-iot-operations' namespace + helm.WithReleaseListCondition(conditions.Count(1)), // make sure there is only 1 release left + helm.WithReleaseCondition(helm.DeployedCondition), // and it is deployed + ), + kube.Must(kube.Resource("basic-configmap-1", "azure-iot-operations", helpers.GVK("", "v1", "ConfigMap"))), // make sure the configmap still exists + helm.MustNew("simple-chart-1", "azure-iot-operations"), // make sure simple-chart-1 is still there + helm.MustNewAbsent("simple-chart-2", "azure-iot-operations"), // make sure simple-chart-2 is gone + ), + }, + ), + + Entry( + "It should update the simple-config-map-1 with new data", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"simple-chart-1"}, // (same as previous entry) + SolutionComponents: []string{"basic-configmap-1-modified"}, // (same as previous entry but with new data) + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + //kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-chart-1", nil), // Because the component didn't change + )))), + kube.Must(kube.Instance("instance", "default", kube.WithCondition(conditions.All( // make sure the instance named 'instance' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + //kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.basic-configmap-1", "Updated"), // and the solution component 'basic-configmap-1' is updated + )))), + helm.MustNew("simple-chart-.*", "azure-iot-operations", // releases beginning with 'simple-chart-' in the 'azure-iot-operations' namespace + helm.WithReleaseListCondition(conditions.Count(1)), // make sure there is only 1 release left + helm.WithReleaseCondition(helm.DeployedCondition), // and it is deployed + ), + kube.Must(kube.Resource("basic-configmap-1", "azure-iot-operations", // make sure the configmap still exists + helpers.GVK("", "v1", "ConfigMap"), + kube.WithCondition( + jq.Equality(".data.key", "value-modified"), // and the data is updated + ), + )), + helm.MustNew("simple-chart-1", "azure-iot-operations"), // make sure simple-chart-1 is still there + helm.MustNewAbsent("simple-chart-2", "azure-iot-operations"), // make sure simple-chart-2 is gone + ), + }, + ), + + Entry( + "it should fail the target when component is invalid", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"simple-chart-1-nonexistent"}, // + SolutionComponents: []string{"basic-configmap-1-modified"}, // (same as previous entry) + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningFailedCondition, // and it is failed + //jq.Equality(".status.provisioningStatus.error.details[0].code", "Update Failed"), + //jq.Equality(".status.provisioningStatus.error.details[0].target", "simple-chart-1"), + )))), + successfullInstanceExpectation, + ), + }, + ), + + Entry( + "it should fail the solution when component is invalid", SpecTimeout(60*time.Second), + TestCase{ + TargetComponents: []string{"simple-chart-1-nonexistent"}, // (same as previous entry) + SolutionComponents: []string{"simple-chart-2-nonexistent"}, // + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningFailedCondition, // and it is failed + //jq.Equality(".status.provisioningStatus.error.details[0].code", "Update Failed"), + //jq.Equality(".status.provisioningStatus.error.details[0].target", "simple-chart-1"), + )))), + kube.Must(kube.Instance("instance", "default", kube.WithCondition(conditions.All( // make sure the instance named 'instance' is present in the 'default' namespace + kube.ProvisioningFailedCondition, // and it is failed + //jq.Equality(".status.provisioningStatus.error.details[0].details[0].code", "Update Failed"), + //jq.Equality(".status.provisioningStatus.error.details[0].details[0].target", "simple-chart-2"), + )))), + )}, + ), + + Entry( + "it should update the target with a simple http", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"simple-http"}, + SolutionComponents: []string{}, + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningSucceededCondition, // and it is successfully provisioned + //kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-http", "Updated"), // and the target component 'simple-http' status is updated + )))), + ), + }, + ), + + Entry( + "it should fail to update target with an invalid simple http", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"simple-http-invalid-url"}, + SolutionComponents: []string{}, + Expectation: expectations.All( + kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( // make sure the target named 'target' is present in the 'default' namespace + kube.ProvisioningFailedCondition, // and it is failed + //kube.OperationIdMatchCondition, // and the status operation id matches the metadata operation id + //kube.ProvisioningStatusComponentOutput("target.simple-http", nil), // and the target component 'simple-http-invalid-url' status is failed to update + //jq.Equality(".status.provisioningStatus.error.details[0].target", "simple-http"), + //jq.Equality(".status.provisioningStatus.error.details[0].code", "Update Failed"), + )))), + ), + }, + ), + + // Marking as pending because even though this is correct, i.e, the order of component dependencies is respected, + // the status probe mechanism is broken. So because symphony cannot wait for the CRD to be in an "Established" state + // before deploying the CR, the CRD is not ready when the CR is deployed and the CR deployment fails. + PEntry( + "it should install the resources in the correct dependency order", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"simple-foobar", "foobar-crd"}, // explicitly orderering the CR before the CRD. This is not required but it is a good test + Expectation: expectations.All( + successfullInstanceExpectation, + successfullTargetExpectation, + kube.Must(kube.Resource("foobars.contoso.io", "azure-iot-operations", helpers.GVK("apiextensions.k8s.io", "v1", "CustomResourceDefinition"))), + kube.Must(kube.Resource("simple-foobar", "azure-iot-operations", helpers.GVK("contoso.io", "v1", "FooBar"))), + ), + }, + ), + ) + + Context("with component constraints", func() { + BeforeEach(func(ctx context.Context) { + By("setting the default target props") + targetProps = map[string]string{ + "OS": "windows", + } + }) + + AfterEach(func(ctx context.Context) { + By("resetting the default target props") + targetProps = nil + }) + + DescribeTable("when performing create/update operations", Ordered, runner, + Entry( + "should succeed when the component is deployed to a target with matching constraint", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"mongodb-constraint"}, + Expectation: successfullTargetExpectation, + }, + ), + Entry( + "should remove config map when the component is removed from the target", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"mongodb-constraint"}, + Expectation: successfullTargetExpectation, + }, + ), + Entry( + "should fail when the component constraint references nonexistent property", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"mongodb-constraint"}, + Expectation: failedTargetExpectation, + TargetProperties: map[string]string{ + "Arch": "arm", + }, + }, + ), + ) + }) + Context("with templated expressions", func() { + BeforeEach(func(ctx context.Context) { + By("setting the default target props") + targetProps = map[string]string{ + "OS": "windows", + "color": "blue", + } + }) + + AfterEach(func(ctx context.Context) { + By("resetting the default target props") + targetProps = nil + }) + + DescribeTable("when performing create/update operations with templated expressions", Ordered, runner, + Entry( + "it should succeed when the component is deployed to a target with existing properties", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"expressions-1"}, + Expectation: successfullTargetExpectation, + }, + ), + Entry( + "it should fail when the component is deployed to a target with non-existing properties", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"expressions-1-failed"}, + Expectation: failedTargetExpectation, + }, + ), + Entry( + "it should succeed when solution component has a valid expression", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"expressions-1"}, + SolutionComponents: []string{"expressions-1-soln"}, + Expectation: expectations.All( + successfullTargetExpectation, + successfullInstanceExpectation, + ), + }, + ), + Entry( + "it should fail solution component has an invalid expression", SpecTimeout(specTimeout), + TestCase{ + TargetComponents: []string{"expressions-1"}, + SolutionComponents: []string{"expressions-1-soln-failed"}, + Expectation: expectations.All( + successfullTargetExpectation, + failedInstanceExpectation, + ), + }, + ), + ) + }) + + Context("with instance parameters", func() { + BeforeEach(func(ctx context.Context) { + By("setting the default instance params") + instanceParams = map[string]interface{}{ + "database": "mongodb", + "database_uri": "mongodb://localhost:27017", + } + }) + + AfterEach(func(ctx context.Context) { + By("resetting the default instance params") + instanceParams = nil + }) + + DescribeTable("when performing create/update operations", Ordered, runner, + Entry( + "should succeed when the solution component is deployed to a target with the instance parameters", SpecTimeout(specTimeout), + TestCase{ + SolutionComponents: []string{"basic-configmap-1-params"}, + Expectation: successfullInstanceExpectation, + }, + ), + Entry( + "should fail when the solution component with missing parameter is deployed to a target", SpecTimeout(specTimeout), + TestCase{ + SolutionComponents: []string{"basic-configmap-1-params-modified"}, + Expectation: failedInstanceExpectation, + }, + ), + ) + }) +}) diff --git a/test/integration/scenarios/06.ado/delete_test.go b/test/integration/scenarios/06.ado/delete_test.go new file mode 100644 index 000000000..75c29300f --- /dev/null +++ b/test/integration/scenarios/06.ado/delete_test.go @@ -0,0 +1,175 @@ +package scenarios_test + +import ( + "context" + _ "embed" + "time" + + "github.com/eclipse-symphony/symphony/packages/testutils/expectations" + "github.com/eclipse-symphony/symphony/packages/testutils/expectations/helm" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/eclipse-symphony/symphony/test/integration/lib/shell" + "github.com/eclipse-symphony/symphony/test/integration/lib/testhelpers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Delete", Ordered, func() { + var instanceBytes []byte + var targetBytes []byte + var solutionBytes []byte + var specTimeout = 2 * time.Minute + + type DeleteTestCase struct { + TargetComponents []string + SolutionComponents []string + PreDeleteExpectation types.Expectation + UnderlyingDeleteCommand string + OrcResourceToDelete *[]byte + PostDeleteExpectation types.Expectation + } + + BeforeAll(func(ctx context.Context) { + By("installing orchestrator in the cluster") + shell.LocalenvCmd(ctx, "mage cluster:deploy") + + By("setting the default testing lib logger") + logger.SetDefaultLogger(GinkgoWriter.Printf) + }) + + AfterAll(func() { + By("uninstalling orchestrator from the cluster") + err := shell.LocalenvCmd(context.Background(), "mage destroy all") + Expect(err).ToNot(HaveOccurred()) + }) + + JustAfterEach(func(ctx context.Context) { + if CurrentSpecReport().Failed() { + By("dumping cluster state") + testhelpers.DumpClusterState(ctx) + } + }) + + DescribeTable("when performing create/update operations", Ordered, + func(ctx context.Context, testcase DeleteTestCase) { + By("setting the components for the target") + var err error + targetBytes, err = testhelpers.PatchTarget(defaultTargetManifest, testhelpers.TargetOptions{ + ComponentNames: testcase.TargetComponents, + }) + Expect(err).ToNot(HaveOccurred()) + + By("setting the components for the solution") + solutionBytes, err = testhelpers.PatchSolution(defaultSolutionManifest, testhelpers.SolutionOptions{ + ComponentNames: testcase.SolutionComponents, + }) + Expect(err).ToNot(HaveOccurred()) + + By("preparing the instance bytes with a new operation id for the test") + instanceBytes, err = testhelpers.PatchInstance(defaultInstanceManifest, testhelpers.InstanceOptions{}) + Expect(err).ToNot(HaveOccurred()) + + By("deploying the instance") + err = shell.PipeInExec(ctx, "kubectl apply -f -", instanceBytes) + Expect(err).ToNot(HaveOccurred()) + + By("deploying the target") + err = shell.PipeInExec(ctx, "kubectl apply -f -", targetBytes) + Expect(err).ToNot(HaveOccurred()) + + By("deploying the solution") + err = shell.PipeInExec(ctx, "kubectl apply -f -", solutionBytes) + Expect(err).ToNot(HaveOccurred()) + + By("verifying the resources before deletion") + err = testcase.PreDeleteExpectation.Verify(ctx) + Expect(err).ToNot(HaveOccurred()) + + By("deleting the underlying resources") + err = shell.Exec(ctx, testcase.UnderlyingDeleteCommand) + Expect(err).ToNot(HaveOccurred()) + + By("delete the orchestrator resource") + err = shell.PipeInExec(ctx, "kubectl delete -f -", *testcase.OrcResourceToDelete) + Expect(err).ToNot(HaveOccurred()) + + By("verifying the resources after deletion") + err = testcase.PostDeleteExpectation.Verify(ctx) + Expect(err).ToNot(HaveOccurred()) + }, + + Entry( + "it should delete the target when the underlying helm release is already gone", SpecTimeout(specTimeout), + DeleteTestCase{ + TargetComponents: []string{"simple-chart-1"}, + SolutionComponents: []string{}, + PreDeleteExpectation: expectations.All( + successfullInstanceExpectation, + successfullTargetExpectation, + helm.MustNew("simple-chart-1", "azure-iot-operations", helm.WithReleaseCondition(helm.DeployedCondition)), + ), + UnderlyingDeleteCommand: "helm uninstall simple-chart-1 -n azure-iot-operations --wait", + OrcResourceToDelete: &targetBytes, + PostDeleteExpectation: expectations.All( + successfullInstanceExpectation, + absentTargetExpectation, + ), + }, + ), + + Entry( + "it should delete the instance when the underlying helm release is already gone", SpecTimeout(specTimeout), + DeleteTestCase{ + TargetComponents: []string{}, + SolutionComponents: []string{"simple-chart-1"}, + PreDeleteExpectation: expectations.All( + successfullInstanceExpectation, + successfullTargetExpectation, + helm.MustNew("simple-chart-1", "azure-iot-operations", helm.WithReleaseCondition(helm.DeployedCondition)), + ), + UnderlyingDeleteCommand: "helm uninstall simple-chart-1 -n azure-iot-operations --wait", + OrcResourceToDelete: &instanceBytes, + PostDeleteExpectation: expectations.All( + absentInstanceExpectation, + successfullTargetExpectation, + ), + }, + ), + Entry( + "it should delete the target when the underlying kubernetes resource is already gone", SpecTimeout(specTimeout), + DeleteTestCase{ + TargetComponents: []string{"basic-configmap-1"}, + SolutionComponents: []string{}, + PreDeleteExpectation: expectations.All( + successfullInstanceExpectation, + successfullTargetExpectation, + ), + UnderlyingDeleteCommand: "kubectl delete configmap basic-configmap-1 -n azure-iot-operations", + OrcResourceToDelete: &targetBytes, + PostDeleteExpectation: expectations.All( + successfullInstanceExpectation, + absentTargetExpectation, + ), + }, + ), + + Entry( + "it should delete the instance when the underlying kubernetes resource is already gone", SpecTimeout(specTimeout), + DeleteTestCase{ + TargetComponents: []string{}, + SolutionComponents: []string{"basic-configmap-1"}, + PreDeleteExpectation: expectations.All( + successfullInstanceExpectation, + successfullTargetExpectation, + ), + UnderlyingDeleteCommand: "kubectl delete configmap basic-configmap-1 -n azure-iot-operations", + OrcResourceToDelete: &instanceBytes, + PostDeleteExpectation: expectations.All( + absentInstanceExpectation, + successfullTargetExpectation, + ), + }, + ), + ) +}) diff --git a/test/integration/scenarios/06.ado/manifest/instance.yaml b/test/integration/scenarios/06.ado/manifest/instance.yaml new file mode 100644 index 000000000..3e82335dd --- /dev/null +++ b/test/integration/scenarios/06.ado/manifest/instance.yaml @@ -0,0 +1,9 @@ +apiVersion: solution.symphony/v1 +kind: Instance +metadata: + name: instance +spec: + target: + name: target + solution: solution + scope: azure-iot-operations diff --git a/test/integration/scenarios/06.ado/manifest/solution.yaml b/test/integration/scenarios/06.ado/manifest/solution.yaml new file mode 100644 index 000000000..c78e09cae --- /dev/null +++ b/test/integration/scenarios/06.ado/manifest/solution.yaml @@ -0,0 +1,6 @@ +apiVersion: solution.symphony/v1 +kind: Solution +metadata: + name: solution +spec: + components: [] \ No newline at end of file diff --git a/test/integration/scenarios/06.ado/manifest/target.yaml b/test/integration/scenarios/06.ado/manifest/target.yaml new file mode 100644 index 000000000..2b36d6f17 --- /dev/null +++ b/test/integration/scenarios/06.ado/manifest/target.yaml @@ -0,0 +1,25 @@ +apiVersion: fabric.symphony/v1 +kind: Target +metadata: + name: target +spec: + scope: azure-iot-operations + components: [] + topologies: + - bindings: + - config: + inCluster: "true" + provider: providers.target.k8s + role: instance + - config: + inCluster: "true" + provider: providers.target.helm + role: helm.v3 + - config: + inCluster: "true" + provider: providers.target.kubectl + role: yaml.k8s + - config: + inCluster: "true" + provider: providers.target.http + role: http diff --git a/test/integration/scenarios/06.ado/rbac_test.go b/test/integration/scenarios/06.ado/rbac_test.go new file mode 100644 index 000000000..2ea806f81 --- /dev/null +++ b/test/integration/scenarios/06.ado/rbac_test.go @@ -0,0 +1,220 @@ +package scenarios_test + +import ( + "context" + _ "embed" + "fmt" + "time" + + "github.com/eclipse-symphony/symphony/packages/testutils/expectations" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/packages/testutils/types" + "github.com/eclipse-symphony/symphony/test/integration/lib/shell" + "github.com/eclipse-symphony/symphony/test/integration/lib/testhelpers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RBAC", Ordered, func() { + type Rbac struct { + TargetComponents []string + SolutionComponents []string + InstanceScope string + TargetScope string + Expectation types.Expectation + } + type HelmValues = testhelpers.HelmValues + type Array = testhelpers.Array + type TArray[T any] []T + + var instanceBytes []byte + var targetBytes []byte + var solutionBytes []byte + var specTimeout = 3 * time.Minute + var installValues HelmValues + var runRbacTest = func(ctx context.Context, testcase Rbac) { + By("setting the components for the target and scope") + var err error + targetBytes, err = testhelpers.PatchTarget(defaultTargetManifest, testhelpers.TargetOptions{ + ComponentNames: testcase.TargetComponents, + Scope: testcase.TargetScope, + }) + Expect(err).ToNot(HaveOccurred()) + + By("setting the components for the solution") + solutionBytes, err = testhelpers.PatchSolution(defaultSolutionManifest, testhelpers.SolutionOptions{ + ComponentNames: testcase.SolutionComponents, + }) + Expect(err).ToNot(HaveOccurred()) + + By("setting the instance scope") + instanceBytes, err = testhelpers.PatchInstance(defaultInstanceManifest, testhelpers.InstanceOptions{ + Scope: testcase.InstanceScope, + }) + Expect(err).ToNot(HaveOccurred()) + + By("deploying the instance") + err = shell.PipeInExec(ctx, "kubectl apply -f -", instanceBytes) + Expect(err).ToNot(HaveOccurred()) + + By("deploying the target") + err = shell.PipeInExec(ctx, "kubectl apply -f -", targetBytes) + Expect(err).ToNot(HaveOccurred()) + + By("deploying the solution") + err = shell.PipeInExec(ctx, "kubectl apply -f -", solutionBytes) + Expect(err).ToNot(HaveOccurred()) + + By("verifying the resources") + err = testcase.Expectation.Verify(ctx) + Expect(err).ToNot(HaveOccurred()) + } + + BeforeAll(func(ctx context.Context) { + By("setting the default testing lib logger") + logger.SetDefaultLogger(GinkgoWriter.Printf) + }) + + AfterAll(func() { + By("uninstalling orchestrator from the cluster") + err := shell.LocalenvCmd(context.Background(), "mage destroy all") + Expect(err).ToNot(HaveOccurred()) + }) + + JustAfterEach(func(ctx context.Context) { + if CurrentSpecReport().Failed() { + By("dumping cluster state") + testhelpers.DumpClusterState(ctx) + } + }) + + When("orchestrator is installed as cluster admin", func() { + BeforeAll(func(ctx context.Context) { + By("setting the install values") + installValues = testhelpers.HelmValues{ + "rbac": testhelpers.HelmValues{ + "cluster": testhelpers.HelmValues{ + "admin": true, // Grant symphony cluster admin + }, + }, + } + + By("installing orchestrator in the cluster") + err := shell.LocalenvCmd(ctx, fmt.Sprintf("mage cluster:deploywithsettings '%s'", installValues.String())) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterAll(func(ctx context.Context) { + By("removing all instances, targets and solutions from the cluster") + err := testhelpers.CleanupManifests(ctx) + Expect(err).ToNot(HaveOccurred()) + }) + + DescribeTable("when performing CRUD operations", Ordered, runRbacTest, + Entry( + "it succefully install in default namespace", SpecTimeout(specTimeout), + Rbac{ + TargetComponents: []string{"basic-clusterrole"}, + SolutionComponents: []string{"simple-chart-1"}, + Expectation: expectations.All( + successfullInstanceExpectation, + successfullInstanceExpectation, + ), + }, + ), + ) + }) + + When("orchestrator is installed as namespace admin", func() { + BeforeAll(func(ctx context.Context) { + By("setting the install values") + installValues = HelmValues{ + "rbac": HelmValues{ + "cluster": HelmValues{ + "admin": false, // Deny symphony cluster admin + }, + "namespace": HelmValues{ + "releaseNamespaceAdmin": true, // Grant symphony namespace admin (default namespace) + }, + }, + } + + By("installing orchestrator in the cluster") + err := shell.LocalenvCmd(ctx, fmt.Sprintf("mage cluster:deploywithsettings '%s'", installValues.String())) + Expect(err).ToNot(HaveOccurred()) + + }) + + AfterAll(func(ctx context.Context) { + By("removing all instances, targets and solutions from the cluster") + err := testhelpers.CleanupManifests(ctx) + Expect(err).ToNot(HaveOccurred()) + }) + + DescribeTable("when performing CRUD operations", Ordered, runRbacTest, + Entry( + "it succefully install in default namespace", SpecTimeout(specTimeout), + Rbac{ + TargetComponents: []string{"mongodb-configmap"}, // Namespace level resource (configmap) + SolutionComponents: []string{"basic-configmap-1"}, // Namespace level resource (configmap) + TargetScope: "default", // Places the target component in the same namesapce as orchestrator + InstanceScope: "default", // Places the solution component in the same namesapce as orchestrator + Expectation: expectations.All( + successfullInstanceExpectation, + successfullInstanceExpectation, + ), + }, + ), + ) + }) + + When("orchestrator is installed with specific namespace rules", func() { + BeforeAll(func(ctx context.Context) { + By("setting the install values") + installValues = HelmValues{ + "rbac": HelmValues{ + "cluster": HelmValues{ + "admin": false, // Deny symphony cluster admin + }, + "namespace": HelmValues{ + "namespaces": HelmValues{ + "namespace-a": HelmValues{ + "rules": TArray[HelmValues]{{ + "resources": Array{"configmaps"}, + "verbs": Array{"*"}, + "apiGroups": Array{""}, + }}, + }, + }, + }, + }, + } + + By("installing orchestrator in the cluster") + err := shell.LocalenvCmd(ctx, fmt.Sprintf("mage cluster:deploywithsettings '%s'", installValues.String())) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterAll(func(ctx context.Context) { + By("removing all instances, targets and solutions from the cluster") + err := testhelpers.CleanupManifests(ctx) + Expect(err).ToNot(HaveOccurred()) + }) + + DescribeTable("when performing CRUD operations", Ordered, runRbacTest, + Entry( + "it succefully install in default namespace", SpecTimeout(specTimeout), + Rbac{ + TargetComponents: []string{"mongodb-configmap"}, // Namespace level resource (configmap) + SolutionComponents: []string{"basic-configmap-1"}, // Namespace level resource (configmap) + InstanceScope: "namespace-a", // Places the solution component in the allowed namespace + TargetScope: "namespace-a", // Places the target component in the allowed namespace + Expectation: expectations.All( + successfullInstanceExpectation, + successfullInstanceExpectation, + ), + }, + ), + ) + }) +}) diff --git a/test/integration/scenarios/06.ado/suite_test.go b/test/integration/scenarios/06.ado/suite_test.go new file mode 100644 index 000000000..27767e959 --- /dev/null +++ b/test/integration/scenarios/06.ado/suite_test.go @@ -0,0 +1,62 @@ +package scenarios_test + +import ( + "context" + "testing" + "time" + + _ "embed" + + "github.com/eclipse-symphony/symphony/packages/testutils/conditions" + "github.com/eclipse-symphony/symphony/packages/testutils/expectations/kube" + "github.com/eclipse-symphony/symphony/packages/testutils/logger" + "github.com/eclipse-symphony/symphony/test/integration/lib/shell" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +//go:embed manifest/instance.yaml +var defaultInstanceManifest []byte + +//go:embed manifest/target.yaml +var defaultTargetManifest []byte + +//go:embed manifest/solution.yaml +var defaultSolutionManifest []byte + +var successfullTargetExpectation = kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( + kube.ProvisioningSucceededCondition, + //kube.OperationIdMatchCondition, +)))) + +var successfullInstanceExpectation = kube.Must(kube.Instance("instance", "default", kube.WithCondition(conditions.All( + kube.ProvisioningSucceededCondition, + //kube.OperationIdMatchCondition, +)))) + +var failedTargetExpectation = kube.Must(kube.Target("target", "default", kube.WithCondition(conditions.All( + kube.ProvisioningFailedCondition, + //kube.OperationIdMatchCondition, +)))) + +var failedInstanceExpectation = kube.Must(kube.Instance("instance", "default", kube.WithCondition(conditions.All( + kube.ProvisioningFailedCondition, + //kube.OperationIdMatchCondition, +)))) + +var absentInstanceExpectation = kube.Must(kube.AbsentInstance("instance", "default")) +var absentTargetExpectation = kube.Must(kube.AbsentTarget("target", "default")) + +var _ = BeforeSuite(func(ctx context.Context) { + // err := shell.LocalenvCmd(ctx, "mage cluster:load") + // Expect(err).ToNot(HaveOccurred()) + + shell.LocalenvCmd(ctx, "mage cluster:deploy") + + logger.SetDefaultLogger(GinkgoWriter.Printf) +}, NodeTimeout(5*time.Minute)) + +func TestScenarios(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Scenarios Suite") +} diff --git a/test/localenv/magefile.go b/test/localenv/magefile.go index 5ec3cd6c6..c85e739e5 100644 --- a/test/localenv/magefile.go +++ b/test/localenv/magefile.go @@ -52,8 +52,30 @@ type License mg.Namespace // Deploys the symphony ecosystem to your local Minikube cluster. func (Cluster) Deploy() error { fmt.Printf("Deploying symphony to minikube\n") - helmUpgrade := fmt.Sprintf("helm upgrade %s %s --install -n %s --create-namespace --wait -f ../../packages/helm/symphony/values.yaml -f symphony-ghcr-values.yaml --set symphonyImage.tag=%s --set paiImage.tag=%s", RELEASE_NAME, CHART_PATH, NAMESPACE, DOCKER_TAG, DOCKER_TAG) - return shellcmd.Command(helmUpgrade).Run() + mg.Deps(ensureMinikubeUp) + certsToVerify := []string{"symphony-api-serving-cert ", "symphony-serving-cert"} + commands := []shellcmd.Command{ + shellcmd.Command(fmt.Sprintf("helm upgrade %s %s --install -n %s --create-namespace --wait -f ../../packages/helm/symphony/values.yaml -f symphony-ghcr-values.yaml --set symphonyImage.tag=%s --set paiImage.tag=%s", RELEASE_NAME, CHART_PATH, NAMESPACE, DOCKER_TAG, DOCKER_TAG)), + } + for _, cert := range certsToVerify { + commands = append(commands, shellcmd.Command(fmt.Sprintf("kubectl wait --for=condition=ready certificates %s -n %s --timeout=90s", cert, NAMESPACE))) + } + return shellcmd.RunAll(commands...) +} + +// Deploys the symphony ecosystem to your local Minikube cluster with the provided settings. Note that this would also deploy cert-manager separately. +// E.g. mage deployWithSettings '--set some.key=some_value --set another.key=another_value' +func (Cluster) DeployWithSettings(values string) error { + fmt.Printf("Deploying symphony to minikube with settings, %s\n", values) + mg.Deps(ensureMinikubeUp) + certsToVerify := []string{"symphony-api-serving-cert ", "symphony-serving-cert"} + commands := []shellcmd.Command{ + shellcmd.Command(fmt.Sprintf("helm upgrade %s %s --install -n %s --create-namespace --wait -f ../../packages/helm/symphony/values.yaml -f symphony-ghcr-values.yaml --set symphonyImage.tag=latest --set paiImage.tag=latest %s", RELEASE_NAME, CHART_PATH, NAMESPACE, values)), + } + for _, cert := range certsToVerify { + commands = append(commands, shellcmd.Command(fmt.Sprintf("kubectl wait --for=condition=ready certificates %s -n %s --timeout=90s", cert, NAMESPACE))) + } + return shellcmd.RunAll(commands...) } // Up brings the minikube cluster up with symphony deployed