diff --git a/api/client/client.go b/api/client/client.go index dc64780407767..a60d664d1d45a 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -2719,6 +2719,18 @@ func (c *Client) GetClusterAuditConfig(ctx context.Context) (types.ClusterAuditC return resp, nil } +// CreateAutoUpdateConfig creates AutoUpdateConfig resource. +func (c *Client) CreateAutoUpdateConfig(ctx context.Context, config *autoupdatev1pb.AutoUpdateConfig) (*autoupdatev1pb.AutoUpdateConfig, error) { + client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) + resp, err := client.CreateAutoUpdateConfig(ctx, &autoupdatev1pb.CreateAutoUpdateConfigRequest{ + Config: config, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + // GetAutoUpdateConfig gets AutoUpdateConfig resource. func (c *Client) GetAutoUpdateConfig(ctx context.Context) (*autoupdatev1pb.AutoUpdateConfig, error) { client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) @@ -2729,6 +2741,49 @@ func (c *Client) GetAutoUpdateConfig(ctx context.Context) (*autoupdatev1pb.AutoU return resp, nil } +// UpdateAutoUpdateConfig updates AutoUpdateConfig resource. +func (c *Client) UpdateAutoUpdateConfig(ctx context.Context, config *autoupdatev1pb.AutoUpdateConfig) (*autoupdatev1pb.AutoUpdateConfig, error) { + client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) + resp, err := client.UpdateAutoUpdateConfig(ctx, &autoupdatev1pb.UpdateAutoUpdateConfigRequest{ + Config: config, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// UpsertAutoUpdateConfig updates or creates AutoUpdateConfig resource. +func (c *Client) UpsertAutoUpdateConfig(ctx context.Context, config *autoupdatev1pb.AutoUpdateConfig) (*autoupdatev1pb.AutoUpdateConfig, error) { + client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) + resp, err := client.UpsertAutoUpdateConfig(ctx, &autoupdatev1pb.UpsertAutoUpdateConfigRequest{ + Config: config, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// DeleteAutoUpdateConfig deletes AutoUpdateConfig resource. +func (c *Client) DeleteAutoUpdateConfig(ctx context.Context) error { + client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) + _, err := client.DeleteAutoUpdateConfig(ctx, &autoupdatev1pb.DeleteAutoUpdateConfigRequest{}) + return trace.Wrap(err) +} + +// CreateAutoUpdateVersion creates AutoUpdateVersion resource. +func (c *Client) CreateAutoUpdateVersion(ctx context.Context, version *autoupdatev1pb.AutoUpdateVersion) (*autoupdatev1pb.AutoUpdateVersion, error) { + client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) + resp, err := client.CreateAutoUpdateVersion(ctx, &autoupdatev1pb.CreateAutoUpdateVersionRequest{ + Version: version, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + // GetAutoUpdateVersion gets AutoUpdateVersion resource. func (c *Client) GetAutoUpdateVersion(ctx context.Context) (*autoupdatev1pb.AutoUpdateVersion, error) { client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) @@ -2739,6 +2794,37 @@ func (c *Client) GetAutoUpdateVersion(ctx context.Context) (*autoupdatev1pb.Auto return resp, nil } +// UpdateAutoUpdateVersion updates AutoUpdateVersion resource. +func (c *Client) UpdateAutoUpdateVersion(ctx context.Context, version *autoupdatev1pb.AutoUpdateVersion) (*autoupdatev1pb.AutoUpdateVersion, error) { + client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) + resp, err := client.UpdateAutoUpdateVersion(ctx, &autoupdatev1pb.UpdateAutoUpdateVersionRequest{ + Version: version, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// UpsertAutoUpdateVersion updates or creates AutoUpdateVersion resource. +func (c *Client) UpsertAutoUpdateVersion(ctx context.Context, version *autoupdatev1pb.AutoUpdateVersion) (*autoupdatev1pb.AutoUpdateVersion, error) { + client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) + resp, err := client.UpsertAutoUpdateVersion(ctx, &autoupdatev1pb.UpsertAutoUpdateVersionRequest{ + Version: version, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// DeleteAutoUpdateVersion deletes AutoUpdateVersion resource. +func (c *Client) DeleteAutoUpdateVersion(ctx context.Context) error { + client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) + _, err := client.DeleteAutoUpdateVersion(ctx, &autoupdatev1pb.DeleteAutoUpdateVersionRequest{}) + return trace.Wrap(err) +} + // GetClusterAccessGraphConfig retrieves the Cluster Access Graph configuration from Auth server. func (c *Client) GetClusterAccessGraphConfig(ctx context.Context) (*clusterconfigpb.AccessGraphConfig, error) { rsp, err := c.ClusterConfigClient().GetClusterAccessGraphConfig(ctx, &clusterconfigpb.GetClusterAccessGraphConfigRequest{}) diff --git a/lib/services/resource.go b/lib/services/resource.go index b4f18cf9a0e44..95e61618270b4 100644 --- a/lib/services/resource.go +++ b/lib/services/resource.go @@ -229,6 +229,10 @@ func ParseShortcut(in string) (string, error) { return types.KindServerInfo, nil case types.KindAccessRequest, types.KindAccessRequest + "s", "accessrequest", "accessrequests": return types.KindAccessRequest, nil + case types.KindAutoUpdateConfig: + return types.KindAutoUpdateConfig, nil + case types.KindAutoUpdateVersion: + return types.KindAutoUpdateVersion, nil } return "", trace.BadParameter("unsupported resource: %q - resources should be expressed as 'type/name', for example 'connector/github'", in) } diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index a02b7b859e378..0880de26016b9 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -27,6 +27,7 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport/api/constants" + autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" loginrulepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1" "github.com/gravitational/teleport/api/types" @@ -1384,3 +1385,39 @@ func (c *accessRequestCollection) writeText(w io.Writer, verbose bool) error { _, err := t.AsBuffer().WriteTo(w) return trace.Wrap(err) } + +type autoUpdateConfigCollection struct { + config *autoupdatev1pb.AutoUpdateConfig +} + +func (c *autoUpdateConfigCollection) resources() []types.Resource { + return []types.Resource{types.Resource153ToLegacy(c.config)} +} + +func (c *autoUpdateConfigCollection) writeText(w io.Writer, verbose bool) error { + t := asciitable.MakeTable([]string{"Name", "Tools AutoUpdate Enabled"}) + t.AddRow([]string{ + c.config.GetMetadata().GetName(), + fmt.Sprintf("%v", c.config.GetSpec().GetToolsAutoupdate()), + }) + _, err := t.AsBuffer().WriteTo(w) + return trace.Wrap(err) +} + +type autoUpdateVersionCollection struct { + version *autoupdatev1pb.AutoUpdateVersion +} + +func (c *autoUpdateVersionCollection) resources() []types.Resource { + return []types.Resource{types.Resource153ToLegacy(c.version)} +} + +func (c *autoUpdateVersionCollection) writeText(w io.Writer, verbose bool) error { + t := asciitable.MakeTable([]string{"Name", "Tools AutoUpdate Version"}) + t.AddRow([]string{ + c.version.GetMetadata().GetName(), + fmt.Sprintf("%v", c.version.GetSpec().GetToolsVersion()), + }) + _, err := t.AsBuffer().WriteTo(w) + return trace.Wrap(err) +} diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 02c05e59a74a2..e4c8bc2e5464a 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -39,6 +39,7 @@ import ( apiclient "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" + autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" loginrulepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1" "github.com/gravitational/teleport/api/internalutils/stream" @@ -141,6 +142,8 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, config *servicec types.KindAuditQuery: rc.createAuditQuery, types.KindSecurityReport: rc.createSecurityReport, types.KindServerInfo: rc.createServerInfo, + types.KindAutoUpdateConfig: rc.createAutoUpdateConfig, + types.KindAutoUpdateVersion: rc.createAutoUpdateVersion, } rc.config = config @@ -2380,6 +2383,18 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *authclient case types.KindAccessRequest: resource, err := client.GetAccessRequests(ctx, types.AccessRequestFilter{ID: rc.ref.Name}) return &accessRequestCollection{accessRequests: resource}, trace.Wrap(err) + case types.KindAutoUpdateConfig: + config, err := client.GetAutoUpdateConfig(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + return &autoUpdateConfigCollection{config}, nil + case types.KindAutoUpdateVersion: + version, err := client.GetAutoUpdateVersion(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + return &autoUpdateVersionCollection{version}, nil } return nil, trace.BadParameter("getting %q is not supported", rc.ref.String()) } @@ -2604,3 +2619,41 @@ func (rc *ResourceCommand) createSecurityReport(ctx context.Context, client *aut } return nil } + +func (rc *ResourceCommand) createAutoUpdateConfig(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error { + config, err := services.UnmarshalProtoResource[*autoupdatev1pb.AutoUpdateConfig](raw.Raw) + if err != nil { + return trace.Wrap(err) + } + + if rc.IsForced() { + _, err = client.UpsertAutoUpdateConfig(ctx, config) + } else { + _, err = client.CreateAutoUpdateConfig(ctx, config) + } + if err != nil { + return trace.Wrap(err) + } + + fmt.Println("autoupdate_config has been created") + return nil +} + +func (rc *ResourceCommand) createAutoUpdateVersion(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error { + version, err := services.UnmarshalProtoResource[*autoupdatev1pb.AutoUpdateVersion](raw.Raw) + if err != nil { + return trace.Wrap(err) + } + + if rc.IsForced() { + _, err = client.UpsertAutoUpdateVersion(ctx, version) + } else { + _, err = client.CreateAutoUpdateVersion(ctx, version) + } + if err != nil { + return trace.Wrap(err) + } + + fmt.Println("autoupdate_version has been created") + return nil +} diff --git a/tool/tctl/common/resource_command_test.go b/tool/tctl/common/resource_command_test.go index b7e075120a98d..2ef10964f1f7b 100644 --- a/tool/tctl/common/resource_command_test.go +++ b/tool/tctl/common/resource_command_test.go @@ -33,10 +33,13 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" "k8s.io/apimachinery/pkg/util/yaml" "github.com/gravitational/teleport/api/constants" apidefaults "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/discoveryconfig" "github.com/gravitational/teleport/api/types/header" @@ -1355,6 +1358,14 @@ func TestCreateResources(t *testing.T) { kind: types.KindServerInfo, create: testCreateServerInfo, }, + { + kind: types.KindAutoUpdateConfig, + create: testCreateAutoUpdateConfig, + }, + { + kind: types.KindAutoUpdateVersion, + create: testCreateAutoUpdateVersion, + }, } for _, test := range tests { @@ -1418,3 +1429,73 @@ spec: _, err = runResourceCommand(t, fc, []string{"create", "-f", serverInfoYAMLPath}) require.NoError(t, err) } + +func testCreateAutoUpdateConfig(t *testing.T, fc *config.FileConfig) { + const resourceYAML = `kind: autoupdate_config +metadata: + name: autoupdate-config + revision: 3a43b44a-201e-4d7f-aef1-ae2f6d9811ed +spec: + tools_autoupdate: true +version: v1 +` + _, err := runResourceCommand(t, fc, []string{"get", types.KindAutoUpdateConfig, "--format=json"}) + require.ErrorContains(t, err, "doesn't exist") + + // Create the resource. + resourceYAMLPath := filepath.Join(t.TempDir(), "resource.yaml") + require.NoError(t, os.WriteFile(resourceYAMLPath, []byte(resourceYAML), 0644)) + _, err = runResourceCommand(t, fc, []string{"create", resourceYAMLPath}) + require.NoError(t, err) + + // Get the resource + buf, err := runResourceCommand(t, fc, []string{"get", types.KindAutoUpdateConfig, "--format=json"}) + require.NoError(t, err) + resources := mustDecodeJSON[[]*autoupdate.AutoUpdateConfig](t, buf) + require.Len(t, resources, 1) + + var expected autoupdate.AutoUpdateConfig + require.NoError(t, yaml.Unmarshal([]byte(resourceYAML), &expected)) + + require.Empty(t, cmp.Diff( + []*autoupdate.AutoUpdateConfig{&expected}, + resources, + protocmp.IgnoreFields(&headerv1.Metadata{}, "id", "revision"), + protocmp.Transform(), + )) +} + +func testCreateAutoUpdateVersion(t *testing.T, fc *config.FileConfig) { + const resourceYAML = `kind: autoupdate_version +metadata: + name: autoupdate-version + revision: 3a43b44a-201e-4d7f-aef1-ae2f6d9811ed +spec: + tools_version: 1.2.3 +version: v1 +` + _, err := runResourceCommand(t, fc, []string{"get", types.KindAutoUpdateVersion, "--format=json"}) + require.ErrorContains(t, err, "doesn't exist") + + // Create the resource. + resourceYAMLPath := filepath.Join(t.TempDir(), "resource.yaml") + require.NoError(t, os.WriteFile(resourceYAMLPath, []byte(resourceYAML), 0644)) + _, err = runResourceCommand(t, fc, []string{"create", resourceYAMLPath}) + require.NoError(t, err) + + // Get the resource + buf, err := runResourceCommand(t, fc, []string{"get", types.KindAutoUpdateVersion, "--format=json"}) + require.NoError(t, err) + resources := mustDecodeJSON[[]*autoupdate.AutoUpdateVersion](t, buf) + require.Len(t, resources, 1) + + var expected autoupdate.AutoUpdateVersion + require.NoError(t, yaml.Unmarshal([]byte(resourceYAML), &expected)) + + require.Empty(t, cmp.Diff( + []*autoupdate.AutoUpdateVersion{&expected}, + resources, + protocmp.IgnoreFields(&headerv1.Metadata{}, "id", "revision"), + protocmp.Transform(), + )) +}