diff --git a/NOTICE b/NOTICE index 2607ce1a..644932ff 100644 --- a/NOTICE +++ b/NOTICE @@ -155,9 +155,9 @@ License URL: https://github.com/cloudflare/circl/blob/v1.6.3/LICENSE ---------- Module: github.com/codesphere-cloud/cs-go -Version: v0.21.1 +Version: v0.22.0 License: Apache-2.0 -License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.21.1/LICENSE +License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.22.0/LICENSE ---------- Module: github.com/codesphere-cloud/oms/internal/tmpl @@ -797,9 +797,9 @@ License URL: https://github.com/open-telemetry/opentelemetry-proto-go/blob/otlp/ ---------- Module: go.yaml.in/yaml/v2 -Version: v2.4.3 +Version: v2.4.4 License: Apache-2.0 -License URL: https://github.com/yaml/go-yaml/blob/v2.4.3/LICENSE +License URL: https://github.com/yaml/go-yaml/blob/v2.4.4/LICENSE ---------- Module: go.yaml.in/yaml/v3 @@ -929,9 +929,9 @@ License URL: https://github.com/helm/helm/blob/v4.1.3/LICENSE ---------- Module: k8s.io/api -Version: v0.35.2 +Version: v0.35.3 License: Apache-2.0 -License URL: https://github.com/kubernetes/api/blob/v0.35.2/LICENSE +License URL: https://github.com/kubernetes/api/blob/v0.35.3/LICENSE ---------- Module: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions @@ -941,15 +941,15 @@ License URL: https://github.com/kubernetes/apiextensions-apiserver/blob/v0.35.1/ ---------- Module: k8s.io/apimachinery/pkg -Version: v0.35.2 +Version: v0.35.3 License: Apache-2.0 -License URL: https://github.com/kubernetes/apimachinery/blob/v0.35.2/LICENSE +License URL: https://github.com/kubernetes/apimachinery/blob/v0.35.3/LICENSE ---------- Module: k8s.io/apimachinery/third_party/forked/golang -Version: v0.35.2 +Version: v0.35.3 License: BSD-3-Clause -License URL: https://github.com/kubernetes/apimachinery/blob/v0.35.2/third_party/forked/golang/LICENSE +License URL: https://github.com/kubernetes/apimachinery/blob/v0.35.3/third_party/forked/golang/LICENSE ---------- Module: k8s.io/apiserver/pkg/endpoints/deprecation @@ -959,21 +959,21 @@ License URL: https://github.com/kubernetes/apiserver/blob/v0.35.1/LICENSE ---------- Module: k8s.io/cli-runtime/pkg -Version: v0.35.2 +Version: v0.35.3 License: Apache-2.0 -License URL: https://github.com/kubernetes/cli-runtime/blob/v0.35.2/LICENSE +License URL: https://github.com/kubernetes/cli-runtime/blob/v0.35.3/LICENSE ---------- Module: k8s.io/client-go -Version: v0.35.2 +Version: v0.35.3 License: Apache-2.0 -License URL: https://github.com/kubernetes/client-go/blob/v0.35.2/LICENSE +License URL: https://github.com/kubernetes/client-go/blob/v0.35.3/LICENSE ---------- Module: k8s.io/client-go/third_party/forked/golang/template -Version: v0.35.2 +Version: v0.35.3 License: BSD-3-Clause -License URL: https://github.com/kubernetes/client-go/blob/v0.35.2/third_party/forked/golang/LICENSE +License URL: https://github.com/kubernetes/client-go/blob/v0.35.3/third_party/forked/golang/LICENSE ---------- Module: k8s.io/component-base/version diff --git a/internal/bootstrap/gcp/gcp.go b/internal/bootstrap/gcp/gcp.go index 108425d3..e74e95ed 100644 --- a/internal/bootstrap/gcp/gcp.go +++ b/internal/bootstrap/gcp/gcp.go @@ -485,21 +485,33 @@ func (b *GCPBootstrapper) EnsureProject() error { parent = fmt.Sprintf("folders/%s", b.Env.FolderID) } + deleteProjectAfter, err := calculateProjectExpiryLabel(b.Env.ProjectTTL) + if err != nil { + return fmt.Errorf("failed to calculate project expiry label: %w", err) + } + + labels := map[string]string{ + OMSManagedLabel: "true", + DeleteAfterLabel: deleteProjectAfter, + } + existingProject, err := b.GCPClient.GetProjectByName(b.Env.FolderID, b.Env.ProjectName) if err == nil { b.Env.ProjectID = existingProject.ProjectId b.Env.ProjectName = existingProject.Name + + err := b.GCPClient.UpdateProject(existingProject.ProjectId, labels) + if err != nil { + return fmt.Errorf("failed to update project: %w", err) + } + return nil } + if err.Error() == fmt.Sprintf("project not found: %s", b.Env.ProjectName) { projectId := b.GCPClient.CreateProjectID(b.Env.ProjectName) - projectTTL, err := time.ParseDuration(b.Env.ProjectTTL) - if err != nil { - return fmt.Errorf("invalid project TTL format: %w", err) - } - - _, err = b.GCPClient.CreateProject(parent, projectId, b.Env.ProjectName, projectTTL) + _, err = b.GCPClient.CreateProject(parent, projectId, b.Env.ProjectName, labels) if err != nil { return fmt.Errorf("failed to create project: %w", err) } diff --git a/internal/bootstrap/gcp/gcp_client.go b/internal/bootstrap/gcp/gcp_client.go index 3c049f57..cc3f03b9 100644 --- a/internal/bootstrap/gcp/gcp_client.go +++ b/internal/bootstrap/gcp/gcp_client.go @@ -8,7 +8,6 @@ import ( "fmt" "strings" "sync" - "time" "slices" @@ -30,6 +29,7 @@ import ( "google.golang.org/api/iterator" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/fieldmaskpb" ) // Interface for high-level GCP operations @@ -38,7 +38,8 @@ import ( type GCPClientManager interface { GetProjectByName(folderID string, displayName string) (*resourcemanagerpb.Project, error) CreateProjectID(projectName string) string - CreateProject(parent, projectName, displayName string, ttl time.Duration) (string, error) + CreateProject(parent, projectName, displayName string, labels map[string]string) (string, error) + UpdateProject(projectID string, labels map[string]string) error DeleteProject(projectID string) error IsOMSManagedProject(projectID string) (bool, error) GetBillingInfo(projectID string) (*cloudbilling.ProjectBillingInfo, error) @@ -117,26 +118,18 @@ func (c *GCPClient) CreateProjectID(projectName string) string { // CreateProject creates a new GCP project under the specified parent (folder or organization). // It returns the project ID of the newly created project. -// The project is labeled with 'oms-managed=true' to identify it as created by OMS. -func (c *GCPClient) CreateProject(parent, projectID, displayName string, projectTTL time.Duration) (string, error) { +func (c *GCPClient) CreateProject(parent, projectID, displayName string, labels map[string]string) (string, error) { client, err := resourcemanager.NewProjectsClient(c.ctx) if err != nil { return "", err } defer util.IgnoreError(client.Close) - gcpLabelLayout := "2006-01-02_15-04-05" - deleteProjectAfter := time.Now().UTC().Add(projectTTL).Format(gcpLabelLayout) - deleteProjectAfter = fmt.Sprintf("%s_utc", deleteProjectAfter) // GCP Labels are very limited. This is the only way to add TZ info. - project := &resourcemanagerpb.Project{ ProjectId: projectID, DisplayName: displayName, Parent: parent, - Labels: map[string]string{ - OMSManagedLabel: "true", - DeleteAfterLabel: deleteProjectAfter, - }, + Labels: labels, } op, err := client.CreateProject(c.ctx, &resourcemanagerpb.CreateProjectRequest{Project: project}) @@ -152,6 +145,37 @@ func (c *GCPClient) CreateProject(parent, projectID, displayName string, project return resp.ProjectId, nil } +// UpdateProject updates the project's labels of an existing GCP project. +// Returns an error if the update operation fails or if the project does not exist. +func (c *GCPClient) UpdateProject(projectID string, labels map[string]string) error { + client, err := resourcemanager.NewProjectsClient(c.ctx) + if err != nil { + return err + } + defer util.IgnoreError(client.Close) + + project := &resourcemanagerpb.Project{ + Name: getProjectResourceName(projectID), + Labels: labels, + } + + op, err := client.UpdateProject(c.ctx, &resourcemanagerpb.UpdateProjectRequest{ + Project: project, + UpdateMask: &fieldmaskpb.FieldMask{ + Paths: []string{"labels"}, + }, + }) + if err != nil { + return fmt.Errorf("failed to update project %s with config %v: %w", projectID, project, err) + } + + if _, err = op.Wait(c.ctx); err != nil { + return fmt.Errorf("failed to wait for project update: %w", err) + } + + return nil +} + // DeleteProject deletes the specified GCP project. func (c *GCPClient) DeleteProject(projectID string) error { client, err := resourcemanager.NewProjectsClient(c.ctx) diff --git a/internal/bootstrap/gcp/gcp_test.go b/internal/bootstrap/gcp/gcp_test.go index 34bfa2d4..5cb0706a 100644 --- a/internal/bootstrap/gcp/gcp_test.go +++ b/internal/bootstrap/gcp/gcp_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "strings" - "time" "os" @@ -157,7 +156,7 @@ var _ = Describe("GCP Bootstrapper", func() { // 3. EnsureProject gc.EXPECT().GetProjectByName(mock.Anything, "test-project").Return(nil, fmt.Errorf("project not found: test-project")) gc.EXPECT().CreateProjectID("test-project").Return(projectId) - gc.EXPECT().CreateProject(mock.Anything, mock.Anything, "test-project", time.Hour).Return(mock.Anything, nil) + gc.EXPECT().CreateProject(mock.Anything, mock.Anything, "test-project", mock.Anything).Return(mock.Anything, nil) // 4. EnsureBilling gc.EXPECT().GetBillingInfo(projectId).Return(&cloudbilling.ProjectBillingInfo{BillingEnabled: false}, nil) @@ -537,6 +536,7 @@ var _ = Describe("GCP Bootstrapper", func() { Describe("Valid EnsureProject", func() { It("uses existing project", func() { gc.EXPECT().GetProjectByName(csEnv.FolderID, csEnv.ProjectName).Return(&resourcemanagerpb.Project{ProjectId: "existing-id", Name: "existing-proj"}, nil) + gc.EXPECT().UpdateProject("existing-id", mock.Anything).Return(nil) err := bs.EnsureProject() Expect(err).NotTo(HaveOccurred()) @@ -546,7 +546,7 @@ var _ = Describe("GCP Bootstrapper", func() { It("creates project when missing", func() { gc.EXPECT().GetProjectByName(csEnv.FolderID, csEnv.ProjectName).Return(nil, fmt.Errorf("project not found: %s", csEnv.ProjectName)) gc.EXPECT().CreateProjectID(csEnv.ProjectName).Return("new-proj-id") - gc.EXPECT().CreateProject(csEnv.FolderID, "new-proj-id", csEnv.ProjectName, time.Hour).Return("", nil) + gc.EXPECT().CreateProject(csEnv.FolderID, "new-proj-id", csEnv.ProjectName, mock.Anything).Return("", nil) err := bs.EnsureProject() Expect(err).NotTo(HaveOccurred()) @@ -566,13 +566,22 @@ var _ = Describe("GCP Bootstrapper", func() { It("returns error when CreateProject fails", func() { gc.EXPECT().GetProjectByName("", csEnv.ProjectName).Return(nil, fmt.Errorf("project not found: %s", csEnv.ProjectName)) gc.EXPECT().CreateProjectID(csEnv.ProjectName).Return("fake-id") - gc.EXPECT().CreateProject("", "fake-id", csEnv.ProjectName, time.Hour).Return("", fmt.Errorf("create error")) + gc.EXPECT().CreateProject("", "fake-id", csEnv.ProjectName, mock.Anything).Return("", fmt.Errorf("create error")) err := bs.EnsureProject() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to create project")) Expect(err.Error()).To(ContainSubstring("create error")) }) + + It("returns an error when UpdateProject fails", func() { + gc.EXPECT().GetProjectByName(csEnv.FolderID, csEnv.ProjectName).Return(&resourcemanagerpb.Project{ProjectId: "existing-id", Name: "existing-proj"}, nil) + gc.EXPECT().UpdateProject("existing-id", mock.Anything).Return(fmt.Errorf("failed to update project")) + + err := bs.EnsureProject() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to update project")) + }) }) }) diff --git a/internal/bootstrap/gcp/helper.go b/internal/bootstrap/gcp/helper.go new file mode 100644 index 00000000..179e0a7f --- /dev/null +++ b/internal/bootstrap/gcp/helper.go @@ -0,0 +1,26 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package gcp + +import ( + "fmt" + "time" +) + +// calculateProjectExpiryLabel takes a TTL string (e.g. "24h") and +// returns a formatted UTC timestamp string that is usable as a GCP project label for automatic deletion. +func calculateProjectExpiryLabel(projectTTLStr string) (string, error) { + projectTTL, err := time.ParseDuration(projectTTLStr) + if err != nil { + return "", fmt.Errorf("invalid project TTL format: %w", err) + } + + // prepare label for gcp project deletion in custom UTC time format. + // GCP Labels are very limited. This is an easy way to add date and TZ info in one label. + gcpExpiryLabelLayout := "2006-01-02_15-04-05" + deleteProjectAfter := time.Now().UTC().Add(projectTTL).Format(gcpExpiryLabelLayout) + deleteProjectAfter = fmt.Sprintf("%s_utc", deleteProjectAfter) + + return deleteProjectAfter, nil +} diff --git a/internal/bootstrap/gcp/helper_test.go b/internal/bootstrap/gcp/helper_test.go new file mode 100644 index 00000000..9df25f96 --- /dev/null +++ b/internal/bootstrap/gcp/helper_test.go @@ -0,0 +1,57 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package gcp + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("calculateProjectExpiryLabel", func() { + const customDateFormat string = "2006-01-02_15-04-05_utc" + const customDateFormatRegex string = `^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}_utc$` + + type validTestCase struct { + inputTTL string + expectedDuration time.Duration + } + + DescribeTable("calculating the expiry label from string durations", + func(tc validTestCase) { + label, err := calculateProjectExpiryLabel(tc.inputTTL) + Expect(err).NotTo(HaveOccurred()) + Expect(label).To(MatchRegexp(customDateFormatRegex)) + + parsedTime, err := time.Parse(customDateFormat, label) + Expect(err).NotTo(HaveOccurred()) + + expectedTime := time.Now().UTC().Add(tc.expectedDuration) + + Expect(parsedTime).To(BeTemporally("~", expectedTime, 10*time.Second)) + }, + + // Define test scenarios + Entry("1 Minute", validTestCase{ + inputTTL: "1m", + expectedDuration: 1 * time.Minute, + }), + Entry("1 hour", validTestCase{ + inputTTL: "1h", + expectedDuration: 1 * time.Hour, + }), + Entry("1 Day", validTestCase{ + inputTTL: "24h", + expectedDuration: 24 * time.Hour, + }), + ) + + It("returns an error for invalid duration strings", func() { + label, err := calculateProjectExpiryLabel("1d") // 'd' is not a valid time unit in Go's duration parsing + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid project TTL format")) + Expect(label).To(BeEmpty()) + }) +}) diff --git a/internal/bootstrap/gcp/mocks.go b/internal/bootstrap/gcp/mocks.go index 4fd00cb9..52f01bbc 100644 --- a/internal/bootstrap/gcp/mocks.go +++ b/internal/bootstrap/gcp/mocks.go @@ -11,7 +11,6 @@ import ( mock "github.com/stretchr/testify/mock" "google.golang.org/api/cloudbilling/v1" "google.golang.org/api/dns/v1" - "time" ) // NewMockGCPClientManager creates a new instance of MockGCPClientManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. @@ -377,8 +376,8 @@ func (_c *MockGCPClientManager_CreateInstance_Call) RunAndReturn(run func(projec } // CreateProject provides a mock function for the type MockGCPClientManager -func (_mock *MockGCPClientManager) CreateProject(parent string, projectName string, displayName string, ttl time.Duration) (string, error) { - ret := _mock.Called(parent, projectName, displayName, ttl) +func (_mock *MockGCPClientManager) CreateProject(parent string, projectName string, displayName string, labels map[string]string) (string, error) { + ret := _mock.Called(parent, projectName, displayName, labels) if len(ret) == 0 { panic("no return value specified for CreateProject") @@ -386,16 +385,16 @@ func (_mock *MockGCPClientManager) CreateProject(parent string, projectName stri var r0 string var r1 error - if returnFunc, ok := ret.Get(0).(func(string, string, string, time.Duration) (string, error)); ok { - return returnFunc(parent, projectName, displayName, ttl) + if returnFunc, ok := ret.Get(0).(func(string, string, string, map[string]string) (string, error)); ok { + return returnFunc(parent, projectName, displayName, labels) } - if returnFunc, ok := ret.Get(0).(func(string, string, string, time.Duration) string); ok { - r0 = returnFunc(parent, projectName, displayName, ttl) + if returnFunc, ok := ret.Get(0).(func(string, string, string, map[string]string) string); ok { + r0 = returnFunc(parent, projectName, displayName, labels) } else { r0 = ret.Get(0).(string) } - if returnFunc, ok := ret.Get(1).(func(string, string, string, time.Duration) error); ok { - r1 = returnFunc(parent, projectName, displayName, ttl) + if returnFunc, ok := ret.Get(1).(func(string, string, string, map[string]string) error); ok { + r1 = returnFunc(parent, projectName, displayName, labels) } else { r1 = ret.Error(1) } @@ -411,12 +410,12 @@ type MockGCPClientManager_CreateProject_Call struct { // - parent string // - projectName string // - displayName string -// - ttl time.Duration -func (_e *MockGCPClientManager_Expecter) CreateProject(parent interface{}, projectName interface{}, displayName interface{}, ttl interface{}) *MockGCPClientManager_CreateProject_Call { - return &MockGCPClientManager_CreateProject_Call{Call: _e.mock.On("CreateProject", parent, projectName, displayName, ttl)} +// - labels map[string]string +func (_e *MockGCPClientManager_Expecter) CreateProject(parent interface{}, projectName interface{}, displayName interface{}, labels interface{}) *MockGCPClientManager_CreateProject_Call { + return &MockGCPClientManager_CreateProject_Call{Call: _e.mock.On("CreateProject", parent, projectName, displayName, labels)} } -func (_c *MockGCPClientManager_CreateProject_Call) Run(run func(parent string, projectName string, displayName string, ttl time.Duration)) *MockGCPClientManager_CreateProject_Call { +func (_c *MockGCPClientManager_CreateProject_Call) Run(run func(parent string, projectName string, displayName string, labels map[string]string)) *MockGCPClientManager_CreateProject_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 string if args[0] != nil { @@ -430,9 +429,9 @@ func (_c *MockGCPClientManager_CreateProject_Call) Run(run func(parent string, p if args[2] != nil { arg2 = args[2].(string) } - var arg3 time.Duration + var arg3 map[string]string if args[3] != nil { - arg3 = args[3].(time.Duration) + arg3 = args[3].(map[string]string) } run( arg0, @@ -449,7 +448,7 @@ func (_c *MockGCPClientManager_CreateProject_Call) Return(s string, err error) * return _c } -func (_c *MockGCPClientManager_CreateProject_Call) RunAndReturn(run func(parent string, projectName string, displayName string, ttl time.Duration) (string, error)) *MockGCPClientManager_CreateProject_Call { +func (_c *MockGCPClientManager_CreateProject_Call) RunAndReturn(run func(parent string, projectName string, displayName string, labels map[string]string) (string, error)) *MockGCPClientManager_CreateProject_Call { _c.Call.Return(run) return _c } @@ -1570,3 +1569,60 @@ func (_c *MockGCPClientManager_RemoveIAMRoleBinding_Call) RunAndReturn(run func( _c.Call.Return(run) return _c } + +// UpdateProject provides a mock function for the type MockGCPClientManager +func (_mock *MockGCPClientManager) UpdateProject(projectID string, labels map[string]string) error { + ret := _mock.Called(projectID, labels) + + if len(ret) == 0 { + panic("no return value specified for UpdateProject") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(string, map[string]string) error); ok { + r0 = returnFunc(projectID, labels) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockGCPClientManager_UpdateProject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateProject' +type MockGCPClientManager_UpdateProject_Call struct { + *mock.Call +} + +// UpdateProject is a helper method to define mock.On call +// - projectID string +// - labels map[string]string +func (_e *MockGCPClientManager_Expecter) UpdateProject(projectID interface{}, labels interface{}) *MockGCPClientManager_UpdateProject_Call { + return &MockGCPClientManager_UpdateProject_Call{Call: _e.mock.On("UpdateProject", projectID, labels)} +} + +func (_c *MockGCPClientManager_UpdateProject_Call) Run(run func(projectID string, labels map[string]string)) *MockGCPClientManager_UpdateProject_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 map[string]string + if args[1] != nil { + arg1 = args[1].(map[string]string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockGCPClientManager_UpdateProject_Call) Return(err error) *MockGCPClientManager_UpdateProject_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockGCPClientManager_UpdateProject_Call) RunAndReturn(run func(projectID string, labels map[string]string) error) *MockGCPClientManager_UpdateProject_Call { + _c.Call.Return(run) + return _c +} diff --git a/internal/tmpl/NOTICE b/internal/tmpl/NOTICE index 2607ce1a..644932ff 100644 --- a/internal/tmpl/NOTICE +++ b/internal/tmpl/NOTICE @@ -155,9 +155,9 @@ License URL: https://github.com/cloudflare/circl/blob/v1.6.3/LICENSE ---------- Module: github.com/codesphere-cloud/cs-go -Version: v0.21.1 +Version: v0.22.0 License: Apache-2.0 -License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.21.1/LICENSE +License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.22.0/LICENSE ---------- Module: github.com/codesphere-cloud/oms/internal/tmpl @@ -797,9 +797,9 @@ License URL: https://github.com/open-telemetry/opentelemetry-proto-go/blob/otlp/ ---------- Module: go.yaml.in/yaml/v2 -Version: v2.4.3 +Version: v2.4.4 License: Apache-2.0 -License URL: https://github.com/yaml/go-yaml/blob/v2.4.3/LICENSE +License URL: https://github.com/yaml/go-yaml/blob/v2.4.4/LICENSE ---------- Module: go.yaml.in/yaml/v3 @@ -929,9 +929,9 @@ License URL: https://github.com/helm/helm/blob/v4.1.3/LICENSE ---------- Module: k8s.io/api -Version: v0.35.2 +Version: v0.35.3 License: Apache-2.0 -License URL: https://github.com/kubernetes/api/blob/v0.35.2/LICENSE +License URL: https://github.com/kubernetes/api/blob/v0.35.3/LICENSE ---------- Module: k8s.io/apiextensions-apiserver/pkg/apis/apiextensions @@ -941,15 +941,15 @@ License URL: https://github.com/kubernetes/apiextensions-apiserver/blob/v0.35.1/ ---------- Module: k8s.io/apimachinery/pkg -Version: v0.35.2 +Version: v0.35.3 License: Apache-2.0 -License URL: https://github.com/kubernetes/apimachinery/blob/v0.35.2/LICENSE +License URL: https://github.com/kubernetes/apimachinery/blob/v0.35.3/LICENSE ---------- Module: k8s.io/apimachinery/third_party/forked/golang -Version: v0.35.2 +Version: v0.35.3 License: BSD-3-Clause -License URL: https://github.com/kubernetes/apimachinery/blob/v0.35.2/third_party/forked/golang/LICENSE +License URL: https://github.com/kubernetes/apimachinery/blob/v0.35.3/third_party/forked/golang/LICENSE ---------- Module: k8s.io/apiserver/pkg/endpoints/deprecation @@ -959,21 +959,21 @@ License URL: https://github.com/kubernetes/apiserver/blob/v0.35.1/LICENSE ---------- Module: k8s.io/cli-runtime/pkg -Version: v0.35.2 +Version: v0.35.3 License: Apache-2.0 -License URL: https://github.com/kubernetes/cli-runtime/blob/v0.35.2/LICENSE +License URL: https://github.com/kubernetes/cli-runtime/blob/v0.35.3/LICENSE ---------- Module: k8s.io/client-go -Version: v0.35.2 +Version: v0.35.3 License: Apache-2.0 -License URL: https://github.com/kubernetes/client-go/blob/v0.35.2/LICENSE +License URL: https://github.com/kubernetes/client-go/blob/v0.35.3/LICENSE ---------- Module: k8s.io/client-go/third_party/forked/golang/template -Version: v0.35.2 +Version: v0.35.3 License: BSD-3-Clause -License URL: https://github.com/kubernetes/client-go/blob/v0.35.2/third_party/forked/golang/LICENSE +License URL: https://github.com/kubernetes/client-go/blob/v0.35.3/third_party/forked/golang/LICENSE ---------- Module: k8s.io/component-base/version