diff --git a/.golangci.yml b/.golangci.yml index 229a5838f2462..98859bad6c7d9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -99,10 +99,6 @@ linters-settings: desc: 'use "github.com/google/uuid" instead' - pkg: github.com/pborman/uuid desc: 'use "github.com/google/uuid" instead' - - pkg: github.com/siddontang/go-log/log - desc: 'use "github.com/sirupsen/logrus" instead' - - pkg: github.com/siddontang/go/log - desc: 'use "github.com/sirupsen/logrus" instead' - pkg: github.com/tj/assert desc: 'use "github.com/stretchr/testify/assert" instead' - pkg: go.uber.org/atomic @@ -117,16 +113,29 @@ linters-settings: desc: 'use "github.com/gravitational/teleport/lib/msgraph" instead' - pkg: github.com/cloudflare/cfssl desc: 'use "crypto" or "x/crypto" instead' - # Prevent logrus from being imported by api and e. Once everything in teleport has been converted - # to use log/slog this should be moved into the main block above. - logrus: + # Prevent importing any additional logging libraries. + logging: files: - - '**/api/**' - - '**/e/**' - - '**/lib/srv/**' + # Integrations are still allowed to use logrus becuase they haven't + # been converted to slog yet. Once they use slog, remove this exception. + - '!**/integrations/**' + # The log package still contains the logrus formatter consumed by the integrations. + # Remove this exception when said formatter is deleted. + - '!**/lib/utils/log/**' + - '!**/lib/utils/cli.go' deny: - pkg: github.com/sirupsen/logrus desc: 'use "log/slog" instead' + - pkg: github.com/siddontang/go-log/log + desc: 'use "log/slog" instead' + - pkg: github.com/siddontang/go/log + desc: 'use "log/slog" instead' + - pkg: github.com/mailgun/log + desc: 'use "log/slog" instead' + - pkg: github.com/saferwall/pe/log + desc: 'use "log/slog" instead' + - pkg: golang.org/x/exp/slog + desc: 'use "log/slog" instead' # Prevent importing internal packages in client tools or packages containing # common interfaces consumed by them that are known to bloat binaries or break builds # because they only support a single platform. diff --git a/api/types/resource_153.go b/api/types/resource_153.go index a09c39451cd3d..dbe69a1108466 100644 --- a/api/types/resource_153.go +++ b/api/types/resource_153.go @@ -18,6 +18,8 @@ import ( "encoding/json" "time" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" @@ -124,6 +126,10 @@ func (r *legacyToResource153Adapter) GetVersion() string { // [Resource] type. Implements [ResourceWithLabels] and CloneResource (where the) // wrapped resource supports cloning). // +// Resources153 implemented by proto-generated structs should use ProtoResource153ToLegacy +// instead as it will ensure the protobuf message is properly marshaled to JSON +// with protojson. +// // Note that CheckAndSetDefaults is a noop for the returned resource and // SetSubKind is not implemented and panics on use. func Resource153ToLegacy(r Resource153) Resource { @@ -348,3 +354,36 @@ func (r *resource153ToUnifiedResourceAdapter) CloneResource() ResourceWithLabels clone := r.inner.(ClonableResource153).CloneResource() return Resource153ToUnifiedResource(clone) } + +// ProtoResource153 is a Resource153 implemented by a protobuf-generated struct. +type ProtoResource153 interface { + Resource153 + proto.Message +} + +type protoResource153ToLegacyAdapter struct { + inner ProtoResource153 + resource153ToLegacyAdapter +} + +// MarshalJSON adds support for marshaling the wrapped resource (instead of +// marshaling the adapter itself). +func (r *protoResource153ToLegacyAdapter) MarshalJSON() ([]byte, error) { + return protojson.MarshalOptions{ + UseProtoNames: true, + }.Marshal(r.inner) +} + +// ProtoResource153ToLegacy transforms an RFD 153 style resource implemented by +// a proto-generated struct into a legacy [Resource] type. Implements +// [ResourceWithLabels] and CloneResource (where the wrapped resource supports +// cloning). +// +// Note that CheckAndSetDefaults is a noop for the returned resource and +// SetSubKind is not implemented and panics on use. +func ProtoResource153ToLegacy(r ProtoResource153) Resource { + return &protoResource153ToLegacyAdapter{ + r, + resource153ToLegacyAdapter{r}, + } +} diff --git a/build.assets/tooling/cmd/protoc-gen-eventschema/debug.go b/build.assets/tooling/cmd/protoc-gen-eventschema/debug.go index 575dbe83e17a1..4da934ce4e019 100644 --- a/build.assets/tooling/cmd/protoc-gen-eventschema/debug.go +++ b/build.assets/tooling/cmd/protoc-gen-eventschema/debug.go @@ -25,14 +25,15 @@ package main // inspect what is happening inside the plugin. import ( + "context" "io" + "log/slog" "os" "github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/protoc-gen-gogo/generator" plugin "github.com/gogo/protobuf/protoc-gen-gogo/plugin" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" ) const pluginInputPathEnvironment = "TELEPORT_PROTOC_READ_FILE" @@ -40,10 +41,10 @@ const pluginInputPathEnvironment = "TELEPORT_PROTOC_READ_FILE" func readRequest() (*plugin.CodeGeneratorRequest, error) { inputPath := os.Getenv(pluginInputPathEnvironment) if inputPath == "" { - log.Error(trace.BadParameter("When built with the 'debug' tag, the input path must be set through the environment variable: %s", pluginInputPathEnvironment)) + slog.ErrorContext(context.Background(), "When built with the 'debug' tag, the input path must be set through the TELEPORT_PROTOC_READ_FILE environment variable") os.Exit(-1) } - log.Infof("This is a debug build, the protoc request is read from the file: '%s'", inputPath) + slog.InfoContext(context.Background(), "This is a debug build, the protoc request is read from provided file", "file", inputPath) req, err := readRequestFromFile(inputPath) if err != nil { diff --git a/build.assets/tooling/cmd/protoc-gen-eventschema/main.go b/build.assets/tooling/cmd/protoc-gen-eventschema/main.go index 8480ad31a7594..a2d2210e0386a 100644 --- a/build.assets/tooling/cmd/protoc-gen-eventschema/main.go +++ b/build.assets/tooling/cmd/protoc-gen-eventschema/main.go @@ -19,21 +19,23 @@ package main import ( + "context" + "log/slog" "os" - - log "github.com/sirupsen/logrus" ) func main() { - log.SetLevel(log.DebugLevel) - log.SetOutput(os.Stderr) + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})) + slog.SetDefault(logger) + + ctx := context.Background() req, err := readRequest() if err != nil { - log.WithError(err).Error("Failed to read request") + logger.ErrorContext(ctx, "Failed to read request", "error", err) os.Exit(-1) } if err := handleRequest(req); err != nil { - log.WithError(err).Error("Failed to generate schema") + logger.ErrorContext(ctx, "Failed to generate schema", "error", err) os.Exit(-1) } } diff --git a/build.assets/tooling/cmd/render-helm-ref/main.go b/build.assets/tooling/cmd/render-helm-ref/main.go index 2026c2147672a..5cfa13b35ebeb 100644 --- a/build.assets/tooling/cmd/render-helm-ref/main.go +++ b/build.assets/tooling/cmd/render-helm-ref/main.go @@ -20,15 +20,16 @@ package main import ( "bufio" + "context" "encoding/json" "flag" "fmt" + "log/slog" "os" "regexp" "strings" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" "helm.sh/helm/v3/pkg/chart/loader" ) @@ -52,14 +53,15 @@ func main() { flag.StringVar(&outputPath, "output", "-", "Path of the generated markdown reference, '-' means stdout.") flag.Parse() + ctx := context.Background() if chartPath == "" { - log.Error(trace.BadParameter("chart path must be specified")) + slog.ErrorContext(ctx, "chart path must be specified") os.Exit(1) } reference, err := parseAndRender(chartPath) if err != nil { - log.Errorf("failed parsing chart and rendering reference: %s", err) + slog.ErrorContext(ctx, "failed parsing chart and rendering reference", "error", err) os.Exit(1) } @@ -69,10 +71,10 @@ func main() { } err = os.WriteFile(outputPath, reference, 0o644) if err != nil { - log.Errorf("failed writing file: %s", err) + slog.ErrorContext(ctx, "failed writing file", "error", err) os.Exit(1) } - log.Infof("File %s successfully written", outputPath) + slog.InfoContext(ctx, "File successfully written", "file_path", outputPath) } func parseAndRender(chartPath string) ([]byte, error) { @@ -106,7 +108,10 @@ func parseAndRender(chartPath string) ([]byte, error) { if value.Kind != "" && value.Default == "" { defaultValue, err := getDefaultForValue(value.Name, chrt.Values) if err != nil { - log.Warnf("failed to get default for value %s, error: %s", value.Name, err) + slog.WarnContext(context.Background(), "failed to look up default value", + "value", value.Name, + "error", err, + ) } else { value.Default = string(defaultValue) } @@ -227,7 +232,7 @@ func cleanLine(line string) string { return "" } if line2[0] != '#' { - log.Warnf("Misformatted line: %s", line) + slog.WarnContext(context.Background(), "Misformatted line", "line", line) return "" } return line2[2:] diff --git a/build.assets/tooling/go.mod b/build.assets/tooling/go.mod index e87bf5d8680d4..d726a7c08fd94 100644 --- a/build.assets/tooling/go.mod +++ b/build.assets/tooling/go.mod @@ -10,7 +10,6 @@ require ( github.com/gogo/protobuf v1.3.2 github.com/google/go-github/v41 v41.0.0 github.com/gravitational/trace v1.4.0 - github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 github.com/waigani/diffparser v0.0.0-20190828052634-7391f219313d golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 @@ -48,7 +47,6 @@ require ( github.com/xhit/go-str2duration/v2 v2.1.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.26.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/build.assets/tooling/go.sum b/build.assets/tooling/go.sum index 7e613ad03bde2..7391483f6ceb1 100644 --- a/build.assets/tooling/go.sum +++ b/build.assets/tooling/go.sum @@ -881,8 +881,6 @@ github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfF github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= @@ -1183,7 +1181,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/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.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1197,8 +1194,6 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= diff --git a/docs/pages/admin-guides/access-controls/device-trust/device-management.mdx b/docs/pages/admin-guides/access-controls/device-trust/device-management.mdx index 5ce724adf9461..52c019597f64b 100644 --- a/docs/pages/admin-guides/access-controls/device-trust/device-management.mdx +++ b/docs/pages/admin-guides/access-controls/device-trust/device-management.mdx @@ -13,9 +13,6 @@ token, and removing a trusted device. (!docs/pages/includes/device-trust/prereqs.mdx!) -- For clusters created after v13.3.6, Teleport supports the preset `device-admin` - role to manage devices. - ## Register a trusted device The `tctl` tool is used to manage the device inventory. A device admin is diff --git a/docs/pages/admin-guides/access-controls/device-trust/enforcing-device-trust.mdx b/docs/pages/admin-guides/access-controls/device-trust/enforcing-device-trust.mdx index 82cc5e4dff7c7..619731b02ce44 100644 --- a/docs/pages/admin-guides/access-controls/device-trust/enforcing-device-trust.mdx +++ b/docs/pages/admin-guides/access-controls/device-trust/enforcing-device-trust.mdx @@ -35,11 +35,10 @@ by the `device_trust_mode` authentication setting: (!docs/pages/includes/device-trust/prereqs.mdx!) -- We expect your Teleport cluster to be on version 13.3.6 and above, which has - the preset `require-trusted-device` role. The preset `require-trusted-device` - role does not enforce the use of a trusted device for - [Apps](#app-access-support) or [Desktops](#desktop-access-support). Refer to - their corresponding sections for instructions. +This guide makes use of the preset `require-trusted-device` role, which does not +enforce the use of a trusted device for [Apps](#app-access-support) or +[Desktops](#desktop-access-support). Refer to their corresponding sections for +instructions. ## Role-based trusted device enforcement diff --git a/docs/pages/admin-guides/access-controls/device-trust/guide.mdx b/docs/pages/admin-guides/access-controls/device-trust/guide.mdx index 62a3fe88b4db2..3eedfbc481291 100644 --- a/docs/pages/admin-guides/access-controls/device-trust/guide.mdx +++ b/docs/pages/admin-guides/access-controls/device-trust/guide.mdx @@ -45,46 +45,6 @@ protected with Teleport. root@(=clusterDefaults.nodeIP=):~# ``` - - The preset `require-trusted-device` role, as referenced in this guide, is only available - from Teleport version 13.3.6 and above. For older Teleport cluster, you will need to update - a role with `device_trust_mode: required`. - - For simplicity, the example below updates the preset `access` role but you can update - any existing access granting role which the user is assigned with to enforce Device Trust. - - First, fetch a role so you can update it locally: - ```code - $ tctl edit role/access - ``` - - Edit the role with Device Trust mode: - ```diff - kind: role - metadata: - labels: - teleport.internal/resource-type: preset - name: access - spec: - allow: - logins: - - '{{internal.logins}}' - ... - options: - # require authenticated device check for this role - + device_trust_mode: "required" # add this line - ... - deny: - ... - - ``` - - Save your edits. - - Now that the `access` role is configured with device mode "required", users with - this role will be enforced with Device Trust. - - Once the above prerequisites are met, begin with the following step. ## Step 1/2. Update user profile to enforce Device Trust @@ -145,12 +105,12 @@ $ tsh device enroll --current-device Device "(=devicetrust.asset_tag=)"/macOS registered and enrolled ``` - - The `--current-device` flag tells `tsh` to enroll current device. User must have the preset `editor` + + The `--current-device` flag tells `tsh` to enroll the current device. The user must have the preset `editor` or `device-admin` role to be able to self-enroll their device. For users without the `editor` or - `device-admin` roles, an enrollment token must be generated by a device admin, which can then be + `device-admin` roles, a device admin must generate the an enrollment token, which can then be used to enroll the device. Learn more about manual device enrollment in the - [device management guide](./device-management.mdx#register-a-trusted-device) + [device management guide](./device-management.mdx#register-a-trusted-device). Relogin to fetch updated certificate with device extension: diff --git a/docs/pages/admin-guides/access-controls/guides/headless.mdx b/docs/pages/admin-guides/access-controls/guides/headless.mdx index 2a39c646aef7d..04cfd9a7758fc 100644 --- a/docs/pages/admin-guides/access-controls/guides/headless.mdx +++ b/docs/pages/admin-guides/access-controls/guides/headless.mdx @@ -31,7 +31,7 @@ For example: - Machines for Headless WebAuthn activities have [Linux](../../../installation.mdx), [macOS](../../../installation.mdx) or [Windows](../../../installation.mdx) `tsh` binary installed. - Machines used to approve Headless WebAuthn requests have a Web browser with [WebAuthn support]( https://developers.yubico.com/WebAuthn/WebAuthn_Browser_Support/) or `tsh` binary installed. -- Optional: Teleport Connect v13.3.1+ for [seamless Headless WebAuthn approval](#optional-teleport-connect). +- Optional: Teleport Connect for [seamless Headless WebAuthn approval](#optional-teleport-connect). ## Step 1/3. Configuration @@ -169,9 +169,9 @@ alice@server01 $ ## Optional: Teleport Connect -Teleport Connect v13.3.1+ can also be used to approve Headless WebAuthn logins. -Teleport Connect will automatically detect the Headless WebAuthn login attempt -and allow you to approve or cancel the request. +Teleport Connect can also be used to approve Headless WebAuthn logins. Teleport +Connect will automatically detect the Headless WebAuthn login attempt and allow +you to approve or cancel the request. ![Headless Confirmation](../../../../img/headless/confirmation.png) @@ -183,10 +183,6 @@ You will be prompted to tap your MFA key to complete the approval process. ![Headless WebAuthn Approval](../../../../img/headless/approval.png) - - This also requires a v13.3.1+ Teleport Auth Service. - - ## Troubleshooting ### "WARN: Failed to lock system memory for headless login: ..." diff --git a/docs/pages/admin-guides/access-controls/guides/webauthn.mdx b/docs/pages/admin-guides/access-controls/guides/webauthn.mdx index f6f3bdf4a0a42..425152bc0293a 100644 --- a/docs/pages/admin-guides/access-controls/guides/webauthn.mdx +++ b/docs/pages/admin-guides/access-controls/guides/webauthn.mdx @@ -246,8 +246,8 @@ The `tctl` tool is used to manage the device inventory. A device admin is responsible for managing devices, adding new devices to the inventory and removing devices that are no longer in use. - - Users with the preset `editor` or `device-admin` role (since v13.3.6) + + Users with the preset `editor` or `device-admin` role can register and enroll their device in a single step with the following command: ```code $ tsh device enroll --current-device diff --git a/docs/pages/admin-guides/access-controls/idps/saml-attribute-mapping.mdx b/docs/pages/admin-guides/access-controls/idps/saml-attribute-mapping.mdx index 94e15948a88e9..25a485b253975 100644 --- a/docs/pages/admin-guides/access-controls/idps/saml-attribute-mapping.mdx +++ b/docs/pages/admin-guides/access-controls/idps/saml-attribute-mapping.mdx @@ -73,7 +73,7 @@ Attribute mapping that points to a non-existent value will not be included in SA Predicate expressions for attribute mapping are evaluated against user attributes that can be accessed using evaluation context listed above. -The supported functions and methods are listed below, along with the usage syntax and it's result, evaluated +The supported functions and methods are listed below, along with the usage syntax and its result, evaluated against the following reference user spec file: ```yaml # reference user spec file @@ -200,4 +200,4 @@ $ tctl idp saml test-attribute-mapping --user user.yml --sp sp.yml Print result in format of choice. ```code $ tctl idp saml test-attribute-mapping --user user.yml --sp sp.yml --format (json/yaml) -``` \ No newline at end of file +``` diff --git a/docs/pages/admin-guides/access-controls/idps/saml-guide.mdx b/docs/pages/admin-guides/access-controls/idps/saml-guide.mdx index 79a748fc2a60b..5d1c924c0912c 100644 --- a/docs/pages/admin-guides/access-controls/idps/saml-guide.mdx +++ b/docs/pages/admin-guides/access-controls/idps/saml-guide.mdx @@ -141,7 +141,7 @@ $ tctl create iamshowcase.yaml -If an `entity_descriptor` is provided, it's content takes preference over values provided in `entity_id` and `acs_url`. +If an `entity_descriptor` is provided, its content takes preference over values provided in `entity_id` and `acs_url`. Teleport only tries to fetch or generate entity descriptor when service provider is created for the first time. Subsequent updates require an entity descriptor to be present in the service provider spec. As such, when updating diff --git a/docs/pages/admin-guides/access-controls/sso/oidc.mdx b/docs/pages/admin-guides/access-controls/sso/oidc.mdx index 5efb5f4301033..adf4471d50f77 100644 --- a/docs/pages/admin-guides/access-controls/sso/oidc.mdx +++ b/docs/pages/admin-guides/access-controls/sso/oidc.mdx @@ -21,8 +21,6 @@ policies like: (!docs/pages/includes/commercial-prereqs-tabs.mdx!) - (!docs/pages/includes/tctl.mdx!) -- To control the maximum age of users' sessions before they will be forced to - reauthenticate, your Teleport cluster must be on version 13.3.7 or above. ## Identity Providers @@ -197,13 +195,13 @@ spec: ### Optional: Max age -Teleport has supported setting the `max_age` field since version 13.3.7 to control the -maximum age of users' sessions before they will be forced to reauthenticate. By -default `max_age` is unset, meaning once a user authenticates using OIDC they will -not have to reauthenticate unless the configured OIDC provider forces them to. This -can be set to a duration of time to force users to reauthenticate more often. If -`max_age` is set to zero seconds, users will be forced to reauthenticate with their -OIDC provider every time they authenticate with Teleport. +The `max_age` field controls the maximum age of users' sessions before they will +be forced to reauthenticate. By default `max_age` is unset, meaning once a user +authenticates using OIDC they will not have to reauthenticate unless the +configured OIDC provider forces them to. This can be set to a duration of time +to force users to reauthenticate more often. If `max_age` is set to zero +seconds, users will be forced to reauthenticate with their OIDC provider every +time they authenticate with Teleport. Note that the specified duration must be in whole seconds. `24h` works because that's the same as `1440s`, but `60s500ms` would not be allowed as that is 60.5 seconds. diff --git a/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/aws.mdx b/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/aws.mdx index de375be78b5df..70c41f1ef5cb5 100644 --- a/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/aws.mdx +++ b/docs/pages/admin-guides/deploy-a-cluster/helm-deployments/aws.mdx @@ -109,7 +109,6 @@ You should be aware of these potential limitations and differences when using La that it terminate all inbound TLS traffic itself on the Teleport proxy. This is not directly possible when using a Layer 7 load balancer, so the `tsh` client implements this flow itself [using ALPN connection upgrades](../../../reference/architecture/tls-routing.mdx). -- The use of Teleport and `tsh` v13 or higher is required. Using ACM with an ALB also requires that your cluster has a fully functional installation of the AWS Load Balancer diff --git a/docs/pages/admin-guides/management/operations/db-ca-migrations.mdx b/docs/pages/admin-guides/management/operations/db-ca-migrations.mdx index 7a9cdb32b0a37..a890a38f8bd30 100644 --- a/docs/pages/admin-guides/management/operations/db-ca-migrations.mdx +++ b/docs/pages/admin-guides/management/operations/db-ca-migrations.mdx @@ -12,10 +12,8 @@ the Teleport cluster. Teleport (= db_client_ca.released_version.v15 =) introduced the `db_client` CA to split the responsibilities of the Teleport `db` CA, which was acting as both -host and client CA for Teleport self-hosted database access. -The `db_client` CA was also added as a patch in Teleport -(= db_client_ca.released_version.v13 =) and -(= db_client_ca.released_version.v14 =). +host and client CA for Teleport self-hosted database access. The `db_client` CA +was also added as a patch in Teleport (= db_client_ca.released_version.v14 =). The `db` and `db_client` CAs were both introduced as an automatic migration that occurs after upgrading Teleport. @@ -113,8 +111,7 @@ However, for defense in depth, these databases should only mTLS handshake with a client that presents a `db_client` CA-issued certificate. If your Teleport cluster was upgraded to Teleport -\>=(= db_client_ca.released_version.v13 =), -\>=(= db_client_ca.released_version.v14 =), or +\>=(= db_client_ca.released_version.v14 =) or \>=(= db_client_ca.released_version.v15 =), then you should ensure that you have completed the `db_client` migration. To complete the `db_client` CA migration: @@ -144,8 +141,7 @@ and you have not rotated *both* your `host` and `db` CAs at least once since upgrading, then you should complete the `db` CA migration. If you upgraded an existing cluster to Teleport -\>=(= db_client_ca.released_version.v13 =), -\>=(= db_client_ca.released_version.v14 =), or +\>=(= db_client_ca.released_version.v14 =) or \>=(= db_client_ca.released_version.v15 =) and you have not rotated *both* your `db` and `db_client` CAs at least once since upgrading, then you should complete diff --git a/docs/pages/enroll-resources/database-access/auto-user-provisioning/postgres.mdx b/docs/pages/enroll-resources/database-access/auto-user-provisioning/postgres.mdx index 8618c8a88c099..2fb8c7c1aac83 100644 --- a/docs/pages/enroll-resources/database-access/auto-user-provisioning/postgres.mdx +++ b/docs/pages/enroll-resources/database-access/auto-user-provisioning/postgres.mdx @@ -7,7 +7,7 @@ description: Configure automatic user provisioning for PostgreSQL. ## Prerequisites -- Teleport cluster v13.1 or above with a configured [self-hosted +- Teleport cluster with a configured [self-hosted PostgreSQL](../enroll-self-hosted-databases/postgres-self-hosted.mdx) or [RDS PostgreSQL](../enroll-aws-databases/rds.mdx) database. To configure permissions for database objects like tables, your cluster must be on version diff --git a/docs/pages/enroll-resources/machine-id/deployment/azure.mdx b/docs/pages/enroll-resources/machine-id/deployment/azure.mdx index c78005fbb7598..1639f81cefc4a 100644 --- a/docs/pages/enroll-resources/machine-id/deployment/azure.mdx +++ b/docs/pages/enroll-resources/machine-id/deployment/azure.mdx @@ -12,7 +12,7 @@ On the Azure platform, virtual machines can be assigned a managed identity. The Azure platform will then make available to the virtual machine an attested data document and JWT that allows the virtual machine to act as this identity. This identity can be validated by a third party by attempting to use this token -to fetch it's own identity from the Azure identity service. +to fetch its own identity from the Azure identity service. The `azure` join method instructs the bot to use this attested data document and JWT to prove its identity to the Teleport Auth Server. This allows joining to diff --git a/docs/pages/enroll-resources/server-access/openssh/openssh-manual-install.mdx b/docs/pages/enroll-resources/server-access/openssh/openssh-manual-install.mdx index 31885ef8ba816..0f91c395dcd32 100644 --- a/docs/pages/enroll-resources/server-access/openssh/openssh-manual-install.mdx +++ b/docs/pages/enroll-resources/server-access/openssh/openssh-manual-install.mdx @@ -224,7 +224,7 @@ $ tctl get node/openssh-node When creating host certificates, it is important to specify all the domain names and addresses that refer to your node. If you try to connect to a node with a -name or address that was not specified when creating it's host certificate, +name or address that was not specified when creating its host certificate, Teleport will reject the SSH connection. On your local machine, assign the IP address, fully qualified domain name of diff --git a/docs/pages/includes/database-access/split-db-ca-details.mdx b/docs/pages/includes/database-access/split-db-ca-details.mdx index 8dc448464989f..b50544c463ffd 100644 --- a/docs/pages/includes/database-access/split-db-ca-details.mdx +++ b/docs/pages/includes/database-access/split-db-ca-details.mdx @@ -17,9 +17,8 @@ needs to have a long-lived certificate issued by another CA that its peer node trusts. The split `db` and `db_client` CA architecture was introduced as a security fix -in Teleport versions: -(= db_client_ca.released_version.v13 =), -(= db_client_ca.released_version.v14 =), and +in Teleport versions +(= db_client_ca.released_version.v14 =) and (= db_client_ca.released_version.v15 =). See diff --git a/docs/pages/includes/device-trust/prereqs.mdx b/docs/pages/includes/device-trust/prereqs.mdx index 32699c20b21a7..6447d7c6dd8cf 100644 --- a/docs/pages/includes/device-trust/prereqs.mdx +++ b/docs/pages/includes/device-trust/prereqs.mdx @@ -4,7 +4,7 @@ - To enroll a Windows device, you need: - A device with TPM 2.0. - A user with administrator privileges. This is only required during enrollment. - - `tsh` v13.1.2 or newer. [Download the Windows tsh installer](../../installation.mdx#windows-tsh-and-tctl-clients-only). + - The `tsh` client. [Download the Windows tsh installer](../../installation.mdx#windows-tsh-and-tctl-clients-only). - To enroll a Linux device, you need: - A device with TPM 2.0. - A user with permissions to use the /dev/tpmrm0 device (typically done by diff --git a/docs/pages/includes/helm-reference/zz_generated.teleport-kube-agent.mdx b/docs/pages/includes/helm-reference/zz_generated.teleport-kube-agent.mdx index f7b7542c5311f..3cf958de6fbe6 100644 --- a/docs/pages/includes/helm-reference/zz_generated.teleport-kube-agent.mdx +++ b/docs/pages/includes/helm-reference/zz_generated.teleport-kube-agent.mdx @@ -1127,11 +1127,8 @@ For this reason, it is strongly discouraged to set a custom image when using automatic updates. Teleport Cloud uses automatic updates by default. -Since version 13, hardened distroless images are used by default. You can use -the deprecated debian-based images by setting the value to -`public.ecr.aws/gravitational/teleport`. Those images will be removed with -teleport 15. - +By default, the image contains only the Teleport application and its runtime +dependencies, and does not contain a shell. This setting only takes effect when [`enterprise`](#enterprise) is `false`. When running an enterprise version, you must use [`enterpriseImage`](#enterpriseImage) instead. @@ -1157,11 +1154,8 @@ Teleport-published image. using automatic updates. Teleport Cloud uses automatic updates by default. -Since version 13, hardened distroless images are used by default. -You can use the deprecated debian-based images by setting the value to -`public.ecr.aws/gravitational/teleport-ent`. Those images will be -removed with teleport 15. - +By default, the image contains only the Teleport application and its runtime +dependencies, and does not contain a shell. This setting only takes effect when [`enterprise`](#enterprise) is `true`. When running an enterprise version, you must use [`image`](#image) instead. diff --git a/docs/pages/reference/access-controls/login-rules.mdx b/docs/pages/reference/access-controls/login-rules.mdx index d49782f74feed..fdf24fe1efe45 100644 --- a/docs/pages/reference/access-controls/login-rules.mdx +++ b/docs/pages/reference/access-controls/login-rules.mdx @@ -584,11 +584,6 @@ Expression | Result ### `strings.split` - -The `strings.split` helper was introduced in Teleport v13.3.0. All Auth Service -instances must be running this version or greater before it can be used. - - #### Signature ```go @@ -625,11 +620,6 @@ Expression | Result ### `email.local` - -The `email.local` helper was introduced in Teleport v13.3.0. All Auth Service instances -must be running this version or greater before it can be used. - - #### Signature ```go @@ -661,11 +651,6 @@ Expression | Result ### `regexp.replace` - -The `regexp.replace` helper was introduced in Teleport v13.3.0. All Auth Service instances -must be running this version or greater before it can be used. - - #### Signature ```go diff --git a/docs/pages/reference/access-controls/roles.mdx b/docs/pages/reference/access-controls/roles.mdx index c67dd234b8642..5d04f382a28bf 100644 --- a/docs/pages/reference/access-controls/roles.mdx +++ b/docs/pages/reference/access-controls/roles.mdx @@ -189,13 +189,6 @@ spec: ### Label expressions - -Label expressions are available starting in Teleport version `13.1.1`. -All components of your Teleport cluster must be upgraded to version `13.1.1` -or newer before you will be able to use label expressions. -This includes the Auth Service and **all** Teleport agents. - - Teleport roles also support matching resource labels with predicate expressions when you need to: diff --git a/docs/pages/reference/architecture/trustedclusters.mdx b/docs/pages/reference/architecture/trustedclusters.mdx index 35bf8256cd30c..ee6cd43b4dbe2 100644 --- a/docs/pages/reference/architecture/trustedclusters.mdx +++ b/docs/pages/reference/architecture/trustedclusters.mdx @@ -29,7 +29,7 @@ databases behind a firewall. In the example below, there are three independent clusters: - Cluster `sso.example.com` is a root cluster. This cluster can be used as a single-sign-on entry point -for your organization. It can have it's own independent resources connected to it, or be used just for audit +for your organization. It can have its own independent resources connected to it, or be used just for audit logs collection and single-sign-on. - Clusters `us-east-1a` and `us-east-1b` are two independent clusters in different availability zones. diff --git a/docs/pages/reference/predicate-language.mdx b/docs/pages/reference/predicate-language.mdx index 921436f125519..adeda2509b85d 100644 --- a/docs/pages/reference/predicate-language.mdx +++ b/docs/pages/reference/predicate-language.mdx @@ -76,13 +76,6 @@ See some [examples](cli/cli.mdx) of the different ways you can filter resources. ## Label expressions - -Label expressions are available starting in Teleport version `13.1.1`. -All components of your Teleport cluster must be upgraded to version `13.1.1` -or newer before you will be able to use label expressions. -This includes the Auth Service and **all** Teleport agents. - - Label expressions can be used in Teleport roles to define access to resources with custom logic. Check out the Access Controls diff --git a/e b/e index b486de24a443a..498f643ea9033 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit b486de24a443a9f8eb3e349009af14d11814ff5c +Subproject commit 498f643ea9033b1235359d83c310caadb18305d2 diff --git a/e2e/aws/databases_test.go b/e2e/aws/databases_test.go index e7395ca8e01ce..fee21e7fe52f4 100644 --- a/e2e/aws/databases_test.go +++ b/e2e/aws/databases_test.go @@ -22,17 +22,22 @@ import ( "context" "crypto/tls" "encoding/json" + "errors" "fmt" + "log/slog" "net" "os" "strconv" + "strings" "testing" "time" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/secretsmanager" mysqlclient "github.com/go-mysql-org/go-mysql/client" + "github.com/gravitational/trace" "github.com/jackc/pgconn" + "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -41,6 +46,7 @@ import ( apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils/keys" + "github.com/gravitational/teleport/api/utils/retryutils" "github.com/gravitational/teleport/integration/helpers" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/cryptosuites" @@ -50,6 +56,7 @@ import ( "github.com/gravitational/teleport/lib/srv/db/common" "github.com/gravitational/teleport/lib/srv/db/postgres" "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/lib/utils" ) func TestDatabases(t *testing.T) { @@ -140,29 +147,14 @@ func postgresConnTest(t *testing.T, cluster *helpers.TeleInstance, user string, assert.NotNil(t, pgConn) }, waitForConnTimeout, connRetryTick, "connecting to postgres") - // dont wait forever on the exec or close. - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // Execute a query. - results, err := pgConn.Exec(ctx, query).ReadAll() - require.NoError(t, err) - for i, r := range results { - require.NoError(t, r.Err, "error in result %v", i) - } - - // Disconnect. - err = pgConn.Close(ctx) - require.NoError(t, err) + execPGTestQuery(t, pgConn, query) } // postgresLocalProxyConnTest tests connection to a postgres database via // local proxy tunnel. func postgresLocalProxyConnTest(t *testing.T, cluster *helpers.TeleInstance, user string, route tlsca.RouteToDatabase, query string) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), 2*waitForConnTimeout) - defer cancel() - lp := startLocalALPNProxy(t, ctx, user, cluster, route) + lp := startLocalALPNProxy(t, user, cluster, route) pgconnConfig, err := pgconn.ParseConfig(fmt.Sprintf("postgres://%v/", lp.GetAddr())) require.NoError(t, err) @@ -180,30 +172,36 @@ func postgresLocalProxyConnTest(t *testing.T, cluster *helpers.TeleInstance, use assert.NotNil(t, pgConn) }, waitForConnTimeout, connRetryTick, "connecting to postgres") - // dont wait forever on the exec or close. - ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) + execPGTestQuery(t, pgConn, query) +} + +func execPGTestQuery(t *testing.T, conn *pgconn.PgConn, query string) { + t.Helper() + defer func() { + // dont wait forever to gracefully terminate. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + // Disconnect. + require.NoError(t, conn.Close(ctx)) + }() + + // dont wait forever on the exec. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // Execute a query. - results, err := pgConn.Exec(ctx, query).ReadAll() + results, err := conn.Exec(ctx, query).ReadAll() require.NoError(t, err) for i, r := range results { require.NoError(t, r.Err, "error in result %v", i) } - - // Disconnect. - err = pgConn.Close(ctx) - require.NoError(t, err) } // mysqlLocalProxyConnTest tests connection to a MySQL database via // local proxy tunnel. func mysqlLocalProxyConnTest(t *testing.T, cluster *helpers.TeleInstance, user string, route tlsca.RouteToDatabase, query string) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), 2*waitForConnTimeout) - defer cancel() - - lp := startLocalALPNProxy(t, ctx, user, cluster, route) + lp := startLocalALPNProxy(t, user, cluster, route) var conn *mysqlclient.Conn // retry for a while, the database service might need time to give @@ -223,19 +221,22 @@ func mysqlLocalProxyConnTest(t *testing.T, cluster *helpers.TeleInstance, user s assert.NoError(t, err) assert.NotNil(t, conn) }, waitForConnTimeout, connRetryTick, "connecting to mysql") + defer func() { + // Disconnect. + require.NoError(t, conn.Close()) + }() // Execute a query. require.NoError(t, conn.SetDeadline(time.Now().Add(10*time.Second))) _, err := conn.Execute(query) require.NoError(t, err) - - // Disconnect. - require.NoError(t, conn.Close()) } // startLocalALPNProxy starts local ALPN proxy for the specified database. -func startLocalALPNProxy(t *testing.T, ctx context.Context, user string, cluster *helpers.TeleInstance, route tlsca.RouteToDatabase) *alpnproxy.LocalProxy { +func startLocalALPNProxy(t *testing.T, user string, cluster *helpers.TeleInstance, route tlsca.RouteToDatabase) *alpnproxy.LocalProxy { t.Helper() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) proto, err := alpncommon.ToALPNProtocol(route.Protocol) require.NoError(t, err) @@ -337,7 +338,7 @@ type dbUserLogin struct { port int } -func connectPostgres(t *testing.T, ctx context.Context, info dbUserLogin, dbName string) *pgx.Conn { +func connectPostgres(t *testing.T, ctx context.Context, info dbUserLogin, dbName string) *pgConn { pgCfg, err := pgx.ParseConfig(fmt.Sprintf("postgres://%s:%d/?sslmode=verify-full", info.address, info.port)) require.NoError(t, err) pgCfg.User = info.username @@ -353,7 +354,10 @@ func connectPostgres(t *testing.T, ctx context.Context, info dbUserLogin, dbName t.Cleanup(func() { _ = conn.Close(ctx) }) - return conn + return &pgConn{ + logger: utils.NewSlogLoggerForTests(), + Conn: conn, + } } // secretPassword is used to unmarshal an AWS Secrets Manager @@ -395,3 +399,77 @@ func getSecretValue(t *testing.T, ctx context.Context, secretID string) secretsm require.NotNil(t, secretVal) return *secretVal } + +// pgConn wraps a [pgx.Conn] and adds retries to all Exec calls. +type pgConn struct { + logger *slog.Logger + *pgx.Conn +} + +func (c *pgConn) Exec(ctx context.Context, sql string, args ...interface{}) (pgconn.CommandTag, error) { + var out pgconn.CommandTag + err := withRetry(ctx, c.logger, func() error { + var err error + out, err = c.Conn.Exec(ctx, sql, args...) + return trace.Wrap(err) + }) + return out, trace.Wrap(err) +} + +// withRetry runs a given func a finite number of times until it returns nil +// error or the given context is done. +func withRetry(ctx context.Context, log *slog.Logger, f func() error) error { + linear, err := retryutils.NewLinear(retryutils.LinearConfig{ + First: 0, + Step: 500 * time.Millisecond, + Max: 5 * time.Second, + Jitter: retryutils.HalfJitter, + }) + if err != nil { + return trace.Wrap(err) + } + + // retry a finite number of times before giving up. + const retries = 10 + for i := 0; i < retries; i++ { + err := f() + if err == nil { + return nil + } + + if isRetryable(err) { + log.DebugContext(ctx, "operation failed, retrying", "error", err) + } else { + return trace.Wrap(err) + } + + linear.Inc() + select { + case <-linear.After(): + case <-ctx.Done(): + return trace.Wrap(ctx.Err()) + } + } + return trace.Wrap(err, "too many retries") +} + +// isRetryable returns true if an error can be retried. +func isRetryable(err error) bool { + var pgErr *pgconn.PgError + err = trace.Unwrap(err) + if errors.As(err, &pgErr) { + // https://www.postgresql.org/docs/current/mvcc-serialization-failure-handling.html + switch pgErr.Code { + case pgerrcode.DeadlockDetected, pgerrcode.SerializationFailure, + pgerrcode.UniqueViolation, pgerrcode.ExclusionViolation: + return true + } + } + // Redshift reports this with a vague SQLSTATE XX000, which is the internal + // error code, but this is a serialization error that rolls back the + // transaction, so it should be retried. + if strings.Contains(err.Error(), "conflict with concurrent transaction") { + return true + } + return pgconn.SafeToRetry(err) +} diff --git a/e2e/aws/fixtures_test.go b/e2e/aws/fixtures_test.go index a7f682d799005..1b30f64f382a5 100644 --- a/e2e/aws/fixtures_test.go +++ b/e2e/aws/fixtures_test.go @@ -241,10 +241,6 @@ func withDiscoveryService(t *testing.T, discoveryGroup string, awsMatchers ...ty options.serviceConfigFuncs = append(options.serviceConfigFuncs, func(cfg *servicecfg.Config) { cfg.Discovery.Enabled = true cfg.Discovery.DiscoveryGroup = discoveryGroup - // Reduce the polling interval to speed up the test execution - // in the case of a failure of the first attempt. - // The default polling interval is 5 minutes. - cfg.Discovery.PollInterval = 1 * time.Minute cfg.Discovery.AWSMatchers = append(cfg.Discovery.AWSMatchers, awsMatchers...) }) } diff --git a/e2e/aws/rds_test.go b/e2e/aws/rds_test.go index bb8329cbe6c3f..d536d80a2d183 100644 --- a/e2e/aws/rds_test.go +++ b/e2e/aws/rds_test.go @@ -30,7 +30,6 @@ import ( "github.com/aws/aws-sdk-go-v2/service/rds" mysqlclient "github.com/go-mysql-org/go-mysql/client" "github.com/go-mysql-org/go-mysql/mysql" - "github.com/jackc/pgx/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -440,7 +439,7 @@ func testRDS(t *testing.T) { }) } -func connectAsRDSPostgresAdmin(t *testing.T, ctx context.Context, instanceID string) *pgx.Conn { +func connectAsRDSPostgresAdmin(t *testing.T, ctx context.Context, instanceID string) *pgConn { t.Helper() info := getRDSAdminInfo(t, ctx, instanceID) const dbName = "postgres" @@ -509,7 +508,7 @@ func getRDSAdminInfo(t *testing.T, ctx context.Context, instanceID string) dbUse // provisionRDSPostgresAutoUsersAdmin provisions an admin user suitable for auto-user // provisioning. -func provisionRDSPostgresAutoUsersAdmin(t *testing.T, ctx context.Context, conn *pgx.Conn, adminUser string) { +func provisionRDSPostgresAutoUsersAdmin(t *testing.T, ctx context.Context, conn *pgConn, adminUser string) { t.Helper() // Create the admin user and grant rds_iam so Teleport can auth // with IAM as an existing user. @@ -600,7 +599,7 @@ const ( autoUserWaitStep = 10 * time.Second ) -func waitForPostgresAutoUserDeactivate(t *testing.T, ctx context.Context, conn *pgx.Conn, user string) { +func waitForPostgresAutoUserDeactivate(t *testing.T, ctx context.Context, conn *pgConn, user string) { t.Helper() require.EventuallyWithT(t, func(c *assert.CollectT) { // `Query` documents that it is always safe to attempt to read from the @@ -641,7 +640,7 @@ func waitForPostgresAutoUserDeactivate(t *testing.T, ctx context.Context, conn * }, autoUserWaitDur, autoUserWaitStep, "waiting for auto user %q to be deactivated", user) } -func waitForPostgresAutoUserDrop(t *testing.T, ctx context.Context, conn *pgx.Conn, user string) { +func waitForPostgresAutoUserDrop(t *testing.T, ctx context.Context, conn *pgConn, user string) { t.Helper() require.EventuallyWithT(t, func(c *assert.CollectT) { // `Query` documents that it is always safe to attempt to read from the diff --git a/e2e/aws/redshift_test.go b/e2e/aws/redshift_test.go index 6009e3c9df7af..c8e9bbf418c20 100644 --- a/e2e/aws/redshift_test.go +++ b/e2e/aws/redshift_test.go @@ -27,7 +27,6 @@ import ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/redshift" - "github.com/jackc/pgx/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -96,7 +95,7 @@ func testRedshiftCluster(t *testing.T) { // eachother. labels := db.GetStaticLabels() labels[types.DatabaseAdminLabel] = "test_admin_" + randASCII(t, 6) - cluster.Process.GetAuthServer().UpdateDatabase(ctx, db) + err = cluster.Process.GetAuthServer().UpdateDatabase(ctx, db) require.NoError(t, err) adminUser := mustGetDBAdmin(t, db) @@ -213,7 +212,7 @@ func testRedshiftCluster(t *testing.T) { } } -func connectAsRedshiftClusterAdmin(t *testing.T, ctx context.Context, clusterID string) *pgx.Conn { +func connectAsRedshiftClusterAdmin(t *testing.T, ctx context.Context, clusterID string) *pgConn { t.Helper() info := getRedshiftAdminInfo(t, ctx, clusterID) const dbName = "dev" @@ -247,7 +246,7 @@ func getRedshiftAdminInfo(t *testing.T, ctx context.Context, clusterID string) d // provisionRedshiftAutoUsersAdmin provisions an admin user suitable for auto-user // provisioning. -func provisionRedshiftAutoUsersAdmin(t *testing.T, ctx context.Context, conn *pgx.Conn, adminUser string) { +func provisionRedshiftAutoUsersAdmin(t *testing.T, ctx context.Context, conn *pgConn, adminUser string) { t.Helper() // Don't cleanup the db admin after, because test runs would interfere // with each other. @@ -261,7 +260,7 @@ func provisionRedshiftAutoUsersAdmin(t *testing.T, ctx context.Context, conn *pg } } -func waitForRedshiftAutoUserDeactivate(t *testing.T, ctx context.Context, conn *pgx.Conn, user string) { +func waitForRedshiftAutoUserDeactivate(t *testing.T, ctx context.Context, conn *pgConn, user string) { t.Helper() require.EventuallyWithT(t, func(c *assert.CollectT) { // `Query` documents that it is always safe to attempt to read from the @@ -300,7 +299,7 @@ func waitForRedshiftAutoUserDeactivate(t *testing.T, ctx context.Context, conn * }, autoUserWaitDur, autoUserWaitStep, "waiting for auto user %q to be deactivated", user) } -func waitForRedshiftAutoUserDrop(t *testing.T, ctx context.Context, conn *pgx.Conn, user string) { +func waitForRedshiftAutoUserDrop(t *testing.T, ctx context.Context, conn *pgConn, user string) { t.Helper() require.EventuallyWithT(t, func(c *assert.CollectT) { // `Query` documents that it is always safe to attempt to read from the diff --git a/examples/chart/teleport-cluster/values.yaml b/examples/chart/teleport-cluster/values.yaml index 7e948c15a3570..6a11b492a9879 100644 --- a/examples/chart/teleport-cluster/values.yaml +++ b/examples/chart/teleport-cluster/values.yaml @@ -568,17 +568,13 @@ tls: # Values that you shouldn't need to change. ################################################## -# Container image for the cluster. -# Since version 13, hardened distroless images are used by default. -# You can use the deprecated debian-based images by setting the value to -# `public.ecr.aws/gravitational/teleport`. Those images will be -# removed with teleport 14. +# Container image for the cluster. By default, the image contains only the +# Teleport application and its runtime dependencies, and does not contain a +# shell. image: public.ecr.aws/gravitational/teleport-distroless -# Enterprise version of the image -# Since version 13, hardened distroless images are used by default. -# You can use the deprecated debian-based images by setting the value to -# `public.ecr.aws/gravitational/teleport-ent`. Those images will be -# removed with teleport 14. +# Enterprise version of the image. By default, the image contains only the +# Teleport application and its runtime dependencies, and does not contain a +# shell. enterpriseImage: public.ecr.aws/gravitational/teleport-ent-distroless # Optional array of imagePullSecrets, to use when pulling from a private registry imagePullSecrets: [] diff --git a/examples/chart/teleport-kube-agent/values.yaml b/examples/chart/teleport-kube-agent/values.yaml index c51491783e11c..9b7783e022c11 100644 --- a/examples/chart/teleport-kube-agent/values.yaml +++ b/examples/chart/teleport-kube-agent/values.yaml @@ -891,11 +891,8 @@ adminClusterRoleBinding: # automatic updates. Teleport Cloud uses automatic updates by default. # # -# Since version 13, hardened distroless images are used by default. You can use -# the deprecated debian-based images by setting the value to -# `public.ecr.aws/gravitational/teleport`. Those images will be removed with -# teleport 15. -# +# By default, the image contains only the Teleport application and its runtime +# dependencies, and does not contain a shell. # This setting only takes effect when [`enterprise`](#enterprise) is `false`. # When running an enterprise version, you must use # [`enterpriseImage`](#enterpriseImage) instead. @@ -916,11 +913,8 @@ image: public.ecr.aws/gravitational/teleport-distroless # using automatic updates. Teleport Cloud uses automatic updates by default. # # -# Since version 13, hardened distroless images are used by default. -# You can use the deprecated debian-based images by setting the value to -# `public.ecr.aws/gravitational/teleport-ent`. Those images will be -# removed with teleport 15. -# +# By default, the image contains only the Teleport application and its runtime +# dependencies, and does not contain a shell. # This setting only takes effect when [`enterprise`](#enterprise) is `true`. # When running an enterprise version, you must use [`image`](#image) instead. enterpriseImage: public.ecr.aws/gravitational/teleport-ent-distroless diff --git a/go.mod b/go.mod index e9f8023eb7323..3c35132910093 100644 --- a/go.mod +++ b/go.mod @@ -101,6 +101,7 @@ require ( github.com/fxamacker/cbor/v2 v2.7.0 github.com/ghodss/yaml v1.0.0 github.com/gizak/termui/v3 v3.1.0 + github.com/go-git/go-git/v5 v5.13.1 github.com/go-jose/go-jose/v3 v3.0.3 github.com/go-ldap/ldap/v3 v3.4.10 github.com/go-logr/logr v1.4.2 @@ -308,7 +309,7 @@ require ( github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf // indirect github.com/crewjam/httperr v0.2.0 // indirect github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 // indirect - github.com/cyphar/filepath-securejoin v0.3.4 // indirect + github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/danieljoos/wincred v1.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/daviddengcn/go-colortext v1.0.0 // indirect @@ -341,6 +342,8 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -402,6 +405,7 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect @@ -469,6 +473,7 @@ require ( github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb // indirect github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22 // indirect github.com/pingcap/tidb/pkg/parser v0.0.0-20240930120915-74034d4ac243 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/xattr v0.4.10 // indirect @@ -548,6 +553,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/component-helpers v0.31.3 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/metrics v0.31.3 // indirect @@ -581,4 +587,7 @@ replace ( // least, one of the grpc-go versions above we need to exclude // stats/opentelemetry in order to avoid "ambiguous import" errors on build. // TODO(codingllama): Remove once no dependencies import stats/opentelemetry. -exclude google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a +exclude ( + google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a + google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 +) diff --git a/go.sum b/go.sum index c270f81730944..5665c4f7280c7 100644 --- a/go.sum +++ b/go.sum @@ -778,8 +778,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= -github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE= -github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= +github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E= @@ -1104,8 +1104,8 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 h1:2Dx4IHfC1yHWI12AxQDJM1QbRCDfk6M+blLzlZCXdrc= github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= -github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8= -github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM= +github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= +github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -1247,6 +1247,14 @@ github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3 github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= +github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= +github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= 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= @@ -1689,6 +1697,8 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -1973,6 +1983,8 @@ github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22 h1:2SOzvGvE8beiC1Y4g github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= github.com/pingcap/tidb/pkg/parser v0.0.0-20240930120915-74034d4ac243 h1:B3pF5adXRpuEDfSKY/bV2Lw+pPKtWH4FOaAX3Jx3X54= github.com/pingcap/tidb/pkg/parser v0.0.0-20240930120915-74034d4ac243/go.mod h1:dXcO3Ts6jUVE1VwBZp3wbVdGO4pi9MXY6IvL4L1z62g= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -2081,8 +2093,8 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500/go.mod h1:+njLrG5wSeoG4Ds61rFgEzKvenR2UHbjMoDHsczxly0= github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df h1:S77Pf5fIGMa7oSwp8SQPp7Hb4ZiI38K3RNBKD2LLeEM= github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc= @@ -3148,6 +3160,8 @@ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 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= diff --git a/integration/helpers/instance.go b/integration/helpers/instance.go index 5f652c77b4eea..6d375387a02f6 100644 --- a/integration/helpers/instance.go +++ b/integration/helpers/instance.go @@ -40,7 +40,6 @@ import ( "github.com/gorilla/websocket" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" @@ -327,10 +326,6 @@ type InstanceConfig struct { Priv []byte // Pub is SSH public key of the instance Pub []byte - // Log specifies the logger - // Deprecated: Use Logger instead - // TODO(tross): Delete when e is updated - Log utils.Logger // Logger specifies the logger Logger *slog.Logger // Ports is a collection of instance ports. @@ -354,10 +349,6 @@ func NewInstance(t *testing.T, cfg InstanceConfig) *TeleInstance { cfg.Listeners = StandardListenerSetup(t, &cfg.Fds) } - if cfg.Log == nil { - cfg.Log = logrus.New() - } - if cfg.Logger == nil { cfg.Logger = slog.New(logutils.DiscardHandler{}) } diff --git a/integration/hostuser_test.go b/integration/hostuser_test.go index b5b045c2840b3..f917a95f872a5 100644 --- a/integration/hostuser_test.go +++ b/integration/hostuser_test.go @@ -637,7 +637,7 @@ func TestRootLoginAsHostUser(t *testing.T) { NodeName: Host, Priv: privateKey, Pub: publicKey, - Log: utils.NewLoggerForTests(), + Logger: utils.NewSlogLoggerForTests(), }) // Create a user that can create a host user. @@ -735,7 +735,7 @@ func TestRootStaticHostUsers(t *testing.T) { NodeName: Host, Priv: privateKey, Pub: publicKey, - Log: utils.NewLoggerForTests(), + Logger: utils.NewSlogLoggerForTests(), }) require.NoError(t, instance.Create(t, nil, false, nil)) diff --git a/integration/integration_test.go b/integration/integration_test.go index 43f2a358e51b8..0b48c90b46f39 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -4853,12 +4853,15 @@ func testX11Forwarding(t *testing.T, suite *integrationTestSuite) { assert.Eventually(t, func() bool { output, err := os.ReadFile(tmpFile.Name()) if err == nil && len(output) != 0 { - display <- strings.TrimSpace(string(output)) + select { + case display <- strings.TrimSpace(string(output)): + default: + } return true } return false }, time.Second, 100*time.Millisecond, "failed to read display") - }, 10*time.Second, time.Second) + }, 10*time.Second, 1*time.Second) // Make a new connection to the XServer proxy to confirm that forwarding is working. serverDisplay, err := x11.ParseDisplay(<-display) diff --git a/integrations/event-handler/go.mod b/integrations/event-handler/go.mod index 873e43e03bce5..19d919b359e39 100644 --- a/integrations/event-handler/go.mod +++ b/integrations/event-handler/go.mod @@ -115,7 +115,7 @@ require ( github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf // indirect github.com/crewjam/httperr v0.2.0 // indirect github.com/crewjam/saml v0.4.14 // indirect - github.com/cyphar/filepath-securejoin v0.3.4 // indirect + github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/di-wu/parser v0.3.0 // indirect github.com/di-wu/xsd-datetime v1.0.0 // indirect @@ -351,4 +351,7 @@ replace ( ) // TODO(codingllama): Remove once no dependencies import stats/opentelemetry. -exclude google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a +exclude ( + google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a + google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 +) diff --git a/integrations/event-handler/go.sum b/integrations/event-handler/go.sum index e88669e71da35..1f0435df0d184 100644 --- a/integrations/event-handler/go.sum +++ b/integrations/event-handler/go.sum @@ -883,8 +883,8 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= -github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8= -github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM= +github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= +github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= diff --git a/integrations/lib/logger/logger.go b/integrations/lib/logger/logger.go index a101727ef31e6..7422f03ff906c 100644 --- a/integrations/lib/logger/logger.go +++ b/integrations/lib/logger/logger.go @@ -34,15 +34,6 @@ import ( logutils "github.com/gravitational/teleport/lib/utils/log" ) -// These values are meant to be kept in sync with teleport/lib/config. -// (We avoid importing that package here because integrations must not require CGo) -const ( - // logFileDefaultMode is the preferred permissions mode for log file. - logFileDefaultMode fs.FileMode = 0o644 - // logFileDefaultFlag is the preferred flags set to log file. - logFileDefaultFlag = os.O_WRONLY | os.O_CREATE | os.O_APPEND -) - type Config struct { Output string `toml:"output"` Severity string `toml:"severity"` @@ -108,8 +99,16 @@ func Setup(conf Config) error { } // NewSLogLogger builds a slog.Logger from the logger.Config. -// TODO: this code is adapted from `config.applyLogConfig`, we'll want to deduplicate the logic next time we refactor the logging setup +// TODO(tross): Defer logging initialization to logutils.Initialize and use the +// global slog loggers once integrations has been updated to use slog. func (conf Config) NewSLogLogger() (*slog.Logger, error) { + const ( + // logFileDefaultMode is the preferred permissions mode for log file. + logFileDefaultMode fs.FileMode = 0o644 + // logFileDefaultFlag is the preferred flags set to log file. + logFileDefaultFlag = os.O_WRONLY | os.O_CREATE | os.O_APPEND + ) + var w io.Writer switch conf.Output { case "": @@ -120,7 +119,7 @@ func (conf Config) NewSLogLogger() (*slog.Logger, error) { w = logutils.NewSharedWriter(os.Stdout) case teleport.Syslog: w = os.Stderr - sw, err := utils.NewSyslogWriter() + sw, err := logutils.NewSyslogWriter() if err != nil { slog.Default().ErrorContext(context.Background(), "Failed to switch logging to syslog", "error", err) break diff --git a/integrations/terraform/go.mod b/integrations/terraform/go.mod index 1d9aa5363d8cc..d3240ffff8135 100644 --- a/integrations/terraform/go.mod +++ b/integrations/terraform/go.mod @@ -131,7 +131,7 @@ require ( github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf // indirect github.com/crewjam/httperr v0.2.0 // indirect github.com/crewjam/saml v0.4.14 // indirect - github.com/cyphar/filepath-securejoin v0.3.4 // indirect + github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/di-wu/parser v0.3.0 // indirect github.com/di-wu/xsd-datetime v1.0.0 // indirect @@ -413,4 +413,7 @@ replace ( ) // TODO(codingllama): Remove once no dependencies import stats/opentelemetry. -exclude google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a +exclude ( + google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a + google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 +) diff --git a/integrations/terraform/go.sum b/integrations/terraform/go.sum index 1a6cf422dd62e..106e4e41c759b 100644 --- a/integrations/terraform/go.sum +++ b/integrations/terraform/go.sum @@ -971,8 +971,8 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= -github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8= -github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM= +github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= +github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -1088,12 +1088,12 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= +github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= -github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= -github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= +github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= 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= diff --git a/lib/autoupdate/rollout/reconciler.go b/lib/autoupdate/rollout/reconciler.go index 02f393a58ff8d..2a3282fa4670b 100644 --- a/lib/autoupdate/rollout/reconciler.go +++ b/lib/autoupdate/rollout/reconciler.go @@ -202,11 +202,12 @@ func (r *reconciler) buildRolloutSpec(config *autoupdate.AutoUpdateConfigSpecAge } return &autoupdate.AutoUpdateAgentRolloutSpec{ - StartVersion: version.GetStartVersion(), - TargetVersion: version.GetTargetVersion(), - Schedule: version.GetSchedule(), - AutoupdateMode: mode, - Strategy: strategy, + StartVersion: version.GetStartVersion(), + TargetVersion: version.GetTargetVersion(), + Schedule: version.GetSchedule(), + AutoupdateMode: mode, + Strategy: strategy, + MaintenanceWindowDuration: config.GetMaintenanceWindowDuration(), }, nil } @@ -318,7 +319,7 @@ func (r *reconciler) computeStatus( } status.Groups = groups - err = r.progressRollout(ctx, newSpec.GetStrategy(), status, now) + err = r.progressRollout(ctx, newSpec, status, now) // Failing to progress the update is not a hard failure. // We want to update the status even if something went wrong to surface the failed reconciliation and potential errors to the user. if err != nil { @@ -334,13 +335,13 @@ func (r *reconciler) computeStatus( // groups are updated in place. // If an error is returned, the groups should still be upserted, depending on the strategy, // failing to update a group might not be fatal (other groups can still progress independently). -func (r *reconciler) progressRollout(ctx context.Context, strategyName string, status *autoupdate.AutoUpdateAgentRolloutStatus, now time.Time) error { +func (r *reconciler) progressRollout(ctx context.Context, spec *autoupdate.AutoUpdateAgentRolloutSpec, status *autoupdate.AutoUpdateAgentRolloutStatus, now time.Time) error { for _, strategy := range r.rolloutStrategies { - if strategy.name() == strategyName { - return strategy.progressRollout(ctx, status, now) + if strategy.name() == spec.GetStrategy() { + return strategy.progressRollout(ctx, spec, status, now) } } - return trace.NotImplemented("rollout strategy %q not implemented", strategyName) + return trace.NotImplemented("rollout strategy %q not implemented", spec.GetStrategy()) } // makeGroupStatus creates the autoupdate_agent_rollout.status.groups based on the autoupdate_config. diff --git a/lib/autoupdate/rollout/reconciler_test.go b/lib/autoupdate/rollout/reconciler_test.go index c2739685b7f72..af14136a3d156 100644 --- a/lib/autoupdate/rollout/reconciler_test.go +++ b/lib/autoupdate/rollout/reconciler_test.go @@ -714,7 +714,7 @@ func (f *fakeRolloutStrategy) name() string { return f.strategyName } -func (f *fakeRolloutStrategy) progressRollout(ctx context.Context, status *autoupdate.AutoUpdateAgentRolloutStatus, now time.Time) error { +func (f *fakeRolloutStrategy) progressRollout(ctx context.Context, spec *autoupdate.AutoUpdateAgentRolloutSpec, status *autoupdate.AutoUpdateAgentRolloutStatus, now time.Time) error { f.calls++ return nil } diff --git a/lib/autoupdate/rollout/strategy.go b/lib/autoupdate/rollout/strategy.go index d2f8c2da81f93..d5b8236ce8f90 100644 --- a/lib/autoupdate/rollout/strategy.go +++ b/lib/autoupdate/rollout/strategy.go @@ -40,10 +40,14 @@ const ( // This interface allows us to inject dummy strategies for simpler testing. type rolloutStrategy interface { name() string - progressRollout(context.Context, *autoupdate.AutoUpdateAgentRolloutStatus, time.Time) error + // progressRollout takes the new rollout spec, existing rollout status and current time. + // It updates the status resource in-place to progress the rollout to the next step if possible/needed. + progressRollout(context.Context, *autoupdate.AutoUpdateAgentRolloutSpec, *autoupdate.AutoUpdateAgentRolloutStatus, time.Time) error } -func inWindow(group *autoupdate.AutoUpdateAgentRolloutStatusGroup, now time.Time) (bool, error) { +// inWindow checks if the time is in the group's maintenance window. +// The maintenance window is the semi-open interval: [windowStart, windowEnd). +func inWindow(group *autoupdate.AutoUpdateAgentRolloutStatusGroup, now time.Time, duration time.Duration) (bool, error) { dayOK, err := canUpdateToday(group.ConfigDays, now) if err != nil { return false, trace.Wrap(err, "checking the day of the week") @@ -51,17 +55,22 @@ func inWindow(group *autoupdate.AutoUpdateAgentRolloutStatusGroup, now time.Time if !dayOK { return false, nil } - return int(group.ConfigStartHour) == now.Hour(), nil + + // We compute the theoretical window start and end + windowStart := now.Truncate(24 * time.Hour).Add(time.Duration(group.ConfigStartHour) * time.Hour) + windowEnd := windowStart.Add(duration) + + return !now.Before(windowStart) && now.Before(windowEnd), nil } // rolloutChangedInWindow checks if the rollout got created after the theoretical group start time -func rolloutChangedInWindow(group *autoupdate.AutoUpdateAgentRolloutStatusGroup, now, rolloutStart time.Time) (bool, error) { +func rolloutChangedInWindow(group *autoupdate.AutoUpdateAgentRolloutStatusGroup, now, rolloutStart time.Time, duration time.Duration) (bool, error) { // If the rollout is older than 24h, we know it did not change during the window if now.Sub(rolloutStart) > 24*time.Hour { return false, nil } // Else we check if the rollout happened in the group window. - return inWindow(group, rolloutStart) + return inWindow(group, rolloutStart, duration) } func canUpdateToday(allowedDays []string, now time.Time) (bool, error) { diff --git a/lib/autoupdate/rollout/strategy_haltonerror.go b/lib/autoupdate/rollout/strategy_haltonerror.go index 6ed1a4aae049d..fafc5d5ae30d3 100644 --- a/lib/autoupdate/rollout/strategy_haltonerror.go +++ b/lib/autoupdate/rollout/strategy_haltonerror.go @@ -35,6 +35,7 @@ const ( updateReasonPreviousGroupsNotDone = "previous_groups_not_done" updateReasonUpdateComplete = "update_complete" updateReasonUpdateInProgress = "update_in_progress" + haltOnErrorWindowDuration = time.Hour ) type haltOnErrorStrategy struct { @@ -54,7 +55,7 @@ func newHaltOnErrorStrategy(log *slog.Logger) (rolloutStrategy, error) { }, nil } -func (h *haltOnErrorStrategy) progressRollout(ctx context.Context, status *autoupdate.AutoUpdateAgentRolloutStatus, now time.Time) error { +func (h *haltOnErrorStrategy) progressRollout(ctx context.Context, _ *autoupdate.AutoUpdateAgentRolloutSpec, status *autoupdate.AutoUpdateAgentRolloutStatus, now time.Time) error { // We process every group in order, all the previous groups must be in the DONE state // for the next group to become active. Even if some early groups are not DONE, // later groups might be ACTIVE and need to transition to DONE, so we cannot @@ -81,7 +82,7 @@ func (h *haltOnErrorStrategy) progressRollout(ctx context.Context, status *autou } // Check if the rollout got created after the theoretical group start time - rolloutChangedDuringWindow, err := rolloutChangedInWindow(group, now, status.StartTime.AsTime()) + rolloutChangedDuringWindow, err := rolloutChangedInWindow(group, now, status.StartTime.AsTime(), haltOnErrorWindowDuration) if err != nil { setGroupState(group, group.State, updateReasonReconcilerError, now) return err @@ -149,14 +150,14 @@ func canStartHaltOnError(group, previousGroup *autoupdate.AutoUpdateAgentRollout } } - return inWindow(group, now) + return inWindow(group, now, haltOnErrorWindowDuration) } func isDoneHaltOnError(group *autoupdate.AutoUpdateAgentRolloutStatusGroup, now time.Time) (bool, string) { // Currently we don't implement status reporting from groups/agents. // So we just wait 60 minutes and consider the maintenance done. // This will change as we introduce agent status report and aggregated agent counts. - if group.StartTime.AsTime().Add(time.Hour).Before(now) { + if group.StartTime.AsTime().Add(haltOnErrorWindowDuration).Before(now) { return true, updateReasonUpdateComplete } return false, updateReasonUpdateInProgress diff --git a/lib/autoupdate/rollout/strategy_haltonerror_test.go b/lib/autoupdate/rollout/strategy_haltonerror_test.go index ee3eb8e80ffca..2f59534ddd7db 100644 --- a/lib/autoupdate/rollout/strategy_haltonerror_test.go +++ b/lib/autoupdate/rollout/strategy_haltonerror_test.go @@ -500,7 +500,7 @@ func Test_progressGroupsHaltOnError(t *testing.T) { State: 0, StartTime: tt.rolloutStartTime, } - err := strategy.progressRollout(ctx, status, clock.Now()) + err := strategy.progressRollout(ctx, nil, status, clock.Now()) require.NoError(t, err) // We use require.Equal instead of Elements match because group order matters. // It's not super important for time-based, but is crucial for halt-on-error. diff --git a/lib/autoupdate/rollout/strategy_test.go b/lib/autoupdate/rollout/strategy_test.go index 1348716ba6c1d..0711d4043ae9c 100644 --- a/lib/autoupdate/rollout/strategy_test.go +++ b/lib/autoupdate/rollout/strategy_test.go @@ -95,11 +95,12 @@ func Test_canUpdateToday(t *testing.T) { func Test_inWindow(t *testing.T) { tests := []struct { - name string - group *autoupdate.AutoUpdateAgentRolloutStatusGroup - now time.Time - want bool - wantErr require.ErrorAssertionFunc + name string + group *autoupdate.AutoUpdateAgentRolloutStatusGroup + now time.Time + duration time.Duration + want bool + wantErr require.ErrorAssertionFunc }{ { name: "out of window", @@ -107,9 +108,10 @@ func Test_inWindow(t *testing.T) { ConfigDays: everyWeekdayButSunday, ConfigStartHour: matchingStartHour, }, - now: testSunday, - want: false, - wantErr: require.NoError, + now: testSunday, + duration: time.Hour, + want: false, + wantErr: require.NoError, }, { name: "inside window, wrong hour", @@ -117,9 +119,10 @@ func Test_inWindow(t *testing.T) { ConfigDays: everyWeekday, ConfigStartHour: nonMatchingStartHour, }, - now: testSunday, - want: false, - wantErr: require.NoError, + now: testSunday, + duration: time.Hour, + want: false, + wantErr: require.NoError, }, { name: "inside window, correct hour", @@ -127,9 +130,10 @@ func Test_inWindow(t *testing.T) { ConfigDays: everyWeekday, ConfigStartHour: matchingStartHour, }, - now: testSunday, - want: true, - wantErr: require.NoError, + now: testSunday, + duration: time.Hour, + want: true, + wantErr: require.NoError, }, { name: "invalid weekdays", @@ -137,14 +141,48 @@ func Test_inWindow(t *testing.T) { ConfigDays: []string{"HelloThereGeneralKenobi"}, ConfigStartHour: matchingStartHour, }, - now: testSunday, - want: false, - wantErr: require.Error, + now: testSunday, + duration: time.Hour, + want: false, + wantErr: require.Error, + }, + { + name: "short window", + group: &autoupdate.AutoUpdateAgentRolloutStatusGroup{ + ConfigDays: everyWeekday, + ConfigStartHour: matchingStartHour, + }, + now: testSunday, + duration: time.Second, + want: false, + wantErr: require.NoError, + }, + { + name: "window start time is included", + group: &autoupdate.AutoUpdateAgentRolloutStatusGroup{ + ConfigDays: everyWeekday, + ConfigStartHour: matchingStartHour, + }, + now: testSunday.Truncate(24 * time.Hour).Add(time.Duration(matchingStartHour) * time.Hour), + duration: time.Hour, + want: true, + wantErr: require.NoError, + }, + { + name: "window end time is not included", + group: &autoupdate.AutoUpdateAgentRolloutStatusGroup{ + ConfigDays: everyWeekday, + ConfigStartHour: matchingStartHour, + }, + now: testSunday.Truncate(24 * time.Hour).Add(time.Duration(matchingStartHour+1) * time.Hour), + duration: time.Hour, + want: false, + wantErr: require.NoError, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := inWindow(tt.group, tt.now) + got, err := inWindow(tt.group, tt.now, tt.duration) tt.wantErr(t, err) require.Equal(t, tt.want, got) }) @@ -205,7 +243,7 @@ func Test_rolloutChangedInWindow(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Test execution. - result, err := rolloutChangedInWindow(group, tt.now, tt.rolloutStart) + result, err := rolloutChangedInWindow(group, tt.now, tt.rolloutStart, time.Hour) require.NoError(t, err) require.Equal(t, tt.want, result) }) diff --git a/lib/autoupdate/rollout/strategy_timebased.go b/lib/autoupdate/rollout/strategy_timebased.go index 5d06adf0e5ace..e4df5c6e23789 100644 --- a/lib/autoupdate/rollout/strategy_timebased.go +++ b/lib/autoupdate/rollout/strategy_timebased.go @@ -51,7 +51,13 @@ func newTimeBasedStrategy(log *slog.Logger) (rolloutStrategy, error) { }, nil } -func (h *timeBasedStrategy) progressRollout(ctx context.Context, status *autoupdate.AutoUpdateAgentRolloutStatus, now time.Time) error { +func (h *timeBasedStrategy) progressRollout(ctx context.Context, spec *autoupdate.AutoUpdateAgentRolloutSpec, status *autoupdate.AutoUpdateAgentRolloutStatus, now time.Time) error { + windowDuration := spec.GetMaintenanceWindowDuration().AsDuration() + // Backward compatibility for resources previously created without duration. + if windowDuration == 0 { + windowDuration = haltOnErrorWindowDuration + } + // We always process every group regardless of the order. var errs []error for _, group := range status.Groups { @@ -61,7 +67,7 @@ func (h *timeBasedStrategy) progressRollout(ctx context.Context, status *autoupd // We start any group unstarted group in window. // Done groups can transition back to active if they enter their maintenance window again. // Some agents might have missed the previous windows and might expected to try again. - shouldBeActive, err := inWindow(group, now) + shouldBeActive, err := inWindow(group, now, windowDuration) if err != nil { // In time-based rollouts, groups are not dependent. // Failing to transition a group should affect other groups. @@ -72,7 +78,7 @@ func (h *timeBasedStrategy) progressRollout(ctx context.Context, status *autoupd } // Check if the rollout got created after the theoretical group start time - rolloutChangedDuringWindow, err := rolloutChangedInWindow(group, now, status.StartTime.AsTime()) + rolloutChangedDuringWindow, err := rolloutChangedInWindow(group, now, status.StartTime.AsTime(), windowDuration) if err != nil { setGroupState(group, group.State, updateReasonReconcilerError, now) errs = append(errs, err) @@ -93,7 +99,7 @@ func (h *timeBasedStrategy) progressRollout(ctx context.Context, status *autoupd case autoupdate.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: // The group is currently being updated. We check if the maintenance // is over and if we should transition it to the done state - shouldBeActive, err := inWindow(group, now) + shouldBeActive, err := inWindow(group, now, windowDuration) if err != nil { // In time-based rollouts, groups are not dependent. // Failing to transition a group should affect other groups. diff --git a/lib/autoupdate/rollout/strategy_timebased_test.go b/lib/autoupdate/rollout/strategy_timebased_test.go index 84367f9927c04..6fa6245598a15 100644 --- a/lib/autoupdate/rollout/strategy_timebased_test.go +++ b/lib/autoupdate/rollout/strategy_timebased_test.go @@ -25,6 +25,7 @@ import ( "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" @@ -325,6 +326,11 @@ func Test_progressGroupsTimeBased(t *testing.T) { }, }, } + + spec := &autoupdate.AutoUpdateAgentRolloutSpec{ + MaintenanceWindowDuration: durationpb.New(time.Hour), + } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { status := &autoupdate.AutoUpdateAgentRolloutStatus{ @@ -332,7 +338,7 @@ func Test_progressGroupsTimeBased(t *testing.T) { State: 0, StartTime: tt.rolloutStartTime, } - err := strategy.progressRollout(ctx, status, clock.Now()) + err := strategy.progressRollout(ctx, spec, status, clock.Now()) require.NoError(t, err) // We use require.Equal instead of Elements match because group order matters. // It's not super important for time-based, but is crucial for halt-on-error. diff --git a/lib/client/api.go b/lib/client/api.go index 68084d4833089..ed94462aa9c73 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -35,6 +35,7 @@ import ( "slices" "strconv" "strings" + "sync" "sync/atomic" "time" "unicode/utf8" @@ -2850,6 +2851,21 @@ type execResult struct { exitStatus int } +// sharedWriter is an [io.Writer] implementation that protects +// writes with a mutex. This allows a single [io.Writer] to be shared +// by both logrus and slog without their output clobbering each other. +type sharedWriter struct { + mu sync.Mutex + io.Writer +} + +func (s *sharedWriter) Write(p []byte) (int, error) { + s.mu.Lock() + defer s.mu.Unlock() + + return s.Writer.Write(p) +} + // runCommandOnNodes executes a given bash command on a bunch of remote nodes. func (tc *TeleportClient) runCommandOnNodes(ctx context.Context, clt *ClusterClient, nodes []TargetNode, command []string) error { cluster := clt.ClusterName() @@ -2909,10 +2925,10 @@ func (tc *TeleportClient) runCommandOnNodes(ctx context.Context, clt *ClusterCli } } - stdout := logutils.NewSharedWriter(tc.Stdout) + stdout := &sharedWriter{Writer: tc.Stdout} stderr := stdout if tc.Stdout != tc.Stderr { - stderr = logutils.NewSharedWriter(tc.Stderr) + stderr = &sharedWriter{Writer: tc.Stderr} } for _, node := range nodes { diff --git a/lib/cloud/aws/errors.go b/lib/cloud/aws/errors.go index 576e7f4350ce2..63a9ffa75ca95 100644 --- a/lib/cloud/aws/errors.go +++ b/lib/cloud/aws/errors.go @@ -24,6 +24,7 @@ import ( "strings" awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" + ecstypes "github.com/aws/aws-sdk-go-v2/service/ecs/types" iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" @@ -55,6 +56,10 @@ func ConvertRequestFailureErrorV2(err error) error { return err } +var ( + ecsClusterNotFoundException *ecstypes.ClusterNotFoundException +) + func convertRequestFailureErrorFromStatusCode(statusCode int, requestErr error) error { switch statusCode { case http.StatusForbidden: @@ -69,6 +74,10 @@ func convertRequestFailureErrorFromStatusCode(statusCode int, requestErr error) if strings.Contains(requestErr.Error(), redshiftserverless.ErrCodeAccessDeniedException) { return trace.AccessDenied(requestErr.Error()) } + + if strings.Contains(requestErr.Error(), ecsClusterNotFoundException.ErrorCode()) { + return trace.NotFound(requestErr.Error()) + } } return requestErr // Return unmodified. diff --git a/lib/cloud/aws/errors_test.go b/lib/cloud/aws/errors_test.go index 165456bfdb25b..7f0c3c26b0307 100644 --- a/lib/cloud/aws/errors_test.go +++ b/lib/cloud/aws/errors_test.go @@ -85,6 +85,18 @@ func TestConvertRequestFailureError(t *testing.T) { }, wantIsError: trace.IsNotFound, }, + { + name: "v2 sdk error for ecs ClusterNotFoundException", + inputError: &awshttp.ResponseError{ + ResponseError: &smithyhttp.ResponseError{ + Response: &smithyhttp.Response{Response: &http.Response{ + StatusCode: http.StatusBadRequest, + }}, + Err: trace.Errorf("ClusterNotFoundException"), + }, + }, + wantIsError: trace.IsNotFound, + }, } for _, test := range tests { diff --git a/lib/cloud/awsconfig/awsconfig.go b/lib/cloud/awsconfig/awsconfig.go index 8be00483f4012..7b1cabe5ffe75 100644 --- a/lib/cloud/awsconfig/awsconfig.go +++ b/lib/cloud/awsconfig/awsconfig.go @@ -28,6 +28,7 @@ import ( "github.com/gravitational/trace" "go.opentelemetry.io/otel" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/modules" ) @@ -43,12 +44,25 @@ const ( credentialsSourceIntegration ) -// IntegrationSessionProviderFunc defines a function that creates a credential provider from a region and an integration. -// This is used to generate aws configs for clients that must use an integration instead of ambient credentials. -type IntegrationCredentialProviderFunc func(ctx context.Context, region, integration string) (aws.CredentialsProvider, error) +// OIDCIntegrationClient is an interface that indicates which APIs are +// required to generate an AWS OIDC integration token. +type OIDCIntegrationClient interface { + // GetIntegration returns the specified integration resource. + GetIntegration(ctx context.Context, name string) (types.Integration, error) -// AssumeRoleClientProviderFunc provides an AWS STS assume role API client. -type AssumeRoleClientProviderFunc func(aws.Config) stscreds.AssumeRoleAPIClient + // GenerateAWSOIDCToken generates a token to be used to execute an AWS OIDC + // Integration action. + GenerateAWSOIDCToken(ctx context.Context, integrationName string) (string, error) +} + +// STSClient is a subset of the AWS STS API. +type STSClient interface { + stscreds.AssumeRoleAPIClient + stscreds.AssumeRoleWithWebIdentityAPIClient +} + +// STSClientProviderFunc provides an AWS STS assume role API client. +type STSClientProviderFunc func(aws.Config) STSClient // AssumeRole is an AWS role to assume, optionally with an external ID. type AssumeRole struct { @@ -68,14 +82,16 @@ type options struct { credentialsSource credentialsSource // integration is the name of the integration to be used to fetch the credentials. integration string - // integrationCredentialsProvider is the integration credential provider to use. - integrationCredentialsProvider IntegrationCredentialProviderFunc + // oidcIntegrationClient provides APIs to generate AWS OIDC tokens, which + // can then be exchanged for IAM credentials. + // Required if integration credentials are requested. + oidcIntegrationClient OIDCIntegrationClient // customRetryer is a custom retryer to use for the config. customRetryer func() aws.Retryer // maxRetries is the maximum number of retries to use for the config. maxRetries *int - // assumeRoleClientProvider sets the STS assume role client provider func. - assumeRoleClientProvider AssumeRoleClientProviderFunc + // stsClientProvider sets the STS assume role client provider func. + stsClientProvider STSClientProviderFunc } func buildOptions(optFns ...OptionsFn) (*options, error) { @@ -99,6 +115,9 @@ func (o *options) checkAndSetDefaults() error { if o.integration == "" { return trace.BadParameter("missing integration name") } + if o.oidcIntegrationClient == nil { + return trace.BadParameter("missing AWS OIDC integration client") + } default: return trace.BadParameter("missing credentials source (ambient or integration)") } @@ -106,8 +125,8 @@ func (o *options) checkAndSetDefaults() error { return trace.BadParameter("role chain contains more than 2 roles") } - if o.assumeRoleClientProvider == nil { - o.assumeRoleClientProvider = func(cfg aws.Config) stscreds.AssumeRoleAPIClient { + if o.stsClientProvider == nil { + o.stsClientProvider = func(cfg aws.Config) STSClient { return sts.NewFromConfig(cfg, func(o *sts.Options) { o.TracerProvider = smithyoteltracing.Adapt(otel.GetTracerProvider()) }) @@ -175,18 +194,17 @@ func WithAmbientCredentials() OptionsFn { } } -// WithIntegrationCredentialProvider sets the integration credential provider. -func WithIntegrationCredentialProvider(cred IntegrationCredentialProviderFunc) OptionsFn { +// WithSTSClientProvider sets the STS API client factory func. +func WithSTSClientProvider(fn STSClientProviderFunc) OptionsFn { return func(options *options) { - options.integrationCredentialsProvider = cred + options.stsClientProvider = fn } } -// WithAssumeRoleClientProviderFunc sets the STS API client factory func used to -// assume roles. -func WithAssumeRoleClientProviderFunc(fn AssumeRoleClientProviderFunc) OptionsFn { +// WithOIDCIntegrationClient sets the OIDC integration client. +func WithOIDCIntegrationClient(c OIDCIntegrationClient) OptionsFn { return func(options *options) { - options.assumeRoleClientProvider = fn + options.oidcIntegrationClient = c } } @@ -202,7 +220,7 @@ func GetConfig(ctx context.Context, region string, optFns ...OptionsFn) (aws.Con if err != nil { return aws.Config{}, trace.Wrap(err) } - return getConfigForRoleChain(ctx, cfg, opts.assumeRoles, opts.assumeRoleClientProvider) + return getConfigForRoleChain(ctx, cfg, opts.assumeRoles, opts.stsClientProvider) } // loadDefaultConfig loads a new config. @@ -217,6 +235,7 @@ func buildConfigOptions(region string, cred aws.CredentialsProvider, opts *optio config.WithDefaultRegion(defaultRegion), config.WithRegion(region), config.WithCredentialsProvider(cred), + config.WithCredentialsCacheOptions(awsCredentialsCacheOptions), } if modules.GetModules().IsBoringBinary() { configOpts = append(configOpts, config.WithUseFIPSEndpoint(aws.FIPSEndpointStateEnabled)) @@ -232,27 +251,35 @@ func buildConfigOptions(region string, cred aws.CredentialsProvider, opts *optio // getBaseConfig returns an AWS config without assuming any roles. func getBaseConfig(ctx context.Context, region string, opts *options) (aws.Config, error) { - var cred aws.CredentialsProvider + slog.DebugContext(ctx, "Initializing AWS config from default credential chain", + "region", region, + ) + cfg, err := loadDefaultConfig(ctx, region, nil, opts) + if err != nil { + return aws.Config{}, trace.Wrap(err) + } + if opts.credentialsSource == credentialsSourceIntegration { - if opts.integrationCredentialsProvider == nil { - return aws.Config{}, trace.BadParameter("missing aws integration credential provider") + slog.DebugContext(ctx, "Initializing AWS config with OIDC integration credentials", + "region", region, + "integration", opts.integration, + ) + provider := &integrationCredentialsProvider{ + OIDCIntegrationClient: opts.oidcIntegrationClient, + stsClt: opts.stsClientProvider(cfg), + integrationName: opts.integration, } - - slog.DebugContext(ctx, "Initializing AWS config with integration", "region", region, "integration", opts.integration) - var err error - cred, err = opts.integrationCredentialsProvider(ctx, region, opts.integration) + cc := aws.NewCredentialsCache(provider, awsCredentialsCacheOptions) + _, err := cc.Retrieve(ctx) if err != nil { return aws.Config{}, trace.Wrap(err) } - } else { - slog.DebugContext(ctx, "Initializing AWS config from default credential chain", "region", region) + cfg.Credentials = cc } - - cfg, err := loadDefaultConfig(ctx, region, cred, opts) - return cfg, trace.Wrap(err) + return cfg, nil } -func getConfigForRoleChain(ctx context.Context, cfg aws.Config, roles []AssumeRole, newCltFn AssumeRoleClientProviderFunc) (aws.Config, error) { +func getConfigForRoleChain(ctx context.Context, cfg aws.Config, roles []AssumeRole, newCltFn STSClientProviderFunc) (aws.Config, error) { for _, r := range roles { cfg.Credentials = getAssumeRoleProvider(ctx, newCltFn(cfg), r) } @@ -277,3 +304,41 @@ func getAssumeRoleProvider(ctx context.Context, clt stscreds.AssumeRoleAPIClient } }) } + +// staticIdentityToken provides itself as a JWT []byte token to implement +// [stscreds.IdentityTokenRetriever]. +type staticIdentityToken string + +// GetIdentityToken retrieves the JWT token. +func (t staticIdentityToken) GetIdentityToken() ([]byte, error) { + return []byte(t), nil +} + +// integrationCredentialsProvider provides AWS OIDC integration credentials. +type integrationCredentialsProvider struct { + OIDCIntegrationClient + stsClt STSClient + integrationName string +} + +// Retrieve provides [aws.Credentials] for an AWS OIDC integration. +func (p *integrationCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) { + integration, err := p.GetIntegration(ctx, p.integrationName) + if err != nil { + return aws.Credentials{}, trace.Wrap(err) + } + spec := integration.GetAWSOIDCIntegrationSpec() + if spec == nil { + return aws.Credentials{}, trace.BadParameter("invalid integration subkind, expected awsoidc, got %s", integration.GetSubKind()) + } + token, err := p.GenerateAWSOIDCToken(ctx, p.integrationName) + if err != nil { + return aws.Credentials{}, trace.Wrap(err) + } + cred, err := stscreds.NewWebIdentityRoleProvider( + p.stsClt, + spec.RoleARN, + staticIdentityToken(token), + ).Retrieve(ctx) + return cred, trace.Wrap(err) +} diff --git a/lib/cloud/awsconfig/awsconfig_test.go b/lib/cloud/awsconfig/awsconfig_test.go index 3cb2c4eda3123..2de624fe86c54 100644 --- a/lib/cloud/awsconfig/awsconfig_test.go +++ b/lib/cloud/awsconfig/awsconfig_test.go @@ -24,20 +24,13 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/credentials/stscreds" "github.com/aws/aws-sdk-go-v2/service/sts" ststypes "github.com/aws/aws-sdk-go-v2/service/sts/types" "github.com/gravitational/trace" "github.com/stretchr/testify/require" -) - -type mockCredentialProvider struct { - cred aws.Credentials -} -func (m *mockCredentialProvider) Retrieve(_ context.Context) (aws.Credentials, error) { - return m.cred, nil -} + "github.com/gravitational/teleport/api/types" +) type mockAssumeRoleAPIClient struct{} @@ -57,6 +50,18 @@ func (m *mockAssumeRoleAPIClient) AssumeRole(_ context.Context, params *sts.Assu }, nil } +func (m *mockAssumeRoleAPIClient) AssumeRoleWithWebIdentity(ctx context.Context, in *sts.AssumeRoleWithWebIdentityInput, _ ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) { + expiry := time.Now().Add(60 * time.Minute) + return &sts.AssumeRoleWithWebIdentityOutput{ + Credentials: &ststypes.Credentials{ + AccessKeyId: in.RoleArn, + SecretAccessKey: in.WebIdentityToken, + SessionToken: aws.String("token"), + Expiration: &expiry, + }, + }, nil +} + func TestGetConfigIntegration(t *testing.T) { t.Parallel() @@ -86,32 +91,100 @@ func testGetConfigIntegration(t *testing.T, provider Provider) { dummyIntegration := "integration-test" dummyRegion := "test-region-123" - t.Run("without an integration credential provider, must return missing credential provider error", func(t *testing.T) { + awsOIDCIntegration, err := types.NewIntegrationAWSOIDC( + types.Metadata{Name: "integration-test"}, + &types.AWSOIDCIntegrationSpecV1{ + RoleARN: "arn:aws:sts::123456789012:role/TestRole", + }, + ) + require.NoError(t, err) + fakeIntegrationClt := fakeOIDCIntegrationClient{ + getIntegrationFn: func(context.Context, string) (types.Integration, error) { + return awsOIDCIntegration, nil + }, + getTokenFn: func(context.Context, string) (string, error) { + return "oidc-token", nil + }, + } + + stsClt := func(cfg aws.Config) STSClient { + return &mockAssumeRoleAPIClient{} + } + + t.Run("without an integration client, must return missing credential provider error", func(t *testing.T) { ctx := context.Background() _, err := provider.GetConfig(ctx, dummyRegion, WithCredentialsMaybeIntegration(dummyIntegration)) require.True(t, trace.IsBadParameter(err), "unexpected error: %v", err) - require.ErrorContains(t, err, "missing aws integration credential provider") + require.ErrorContains(t, err, "missing AWS OIDC integration client") + }) + + t.Run("with an integration client, must return integration fetch error", func(t *testing.T) { + ctx := context.Background() + + fakeIntegrationClt := fakeIntegrationClt + fakeIntegrationClt.getIntegrationFn = func(context.Context, string) (types.Integration, error) { + return nil, trace.NotFound("integration not found") + } + _, err := provider.GetConfig(ctx, dummyRegion, + WithCredentialsMaybeIntegration(dummyIntegration), + WithOIDCIntegrationClient(&fakeIntegrationClt), + WithSTSClientProvider(stsClt), + ) + require.Error(t, err) + require.ErrorContains(t, err, "integration not found") + }) + + t.Run("with an integration client, must check for AWS integration subkind", func(t *testing.T) { + ctx := context.Background() + + azureIntegration, err := types.NewIntegrationAzureOIDC( + types.Metadata{Name: "integration-test"}, + &types.AzureOIDCIntegrationSpecV1{ + TenantID: "abc", + ClientID: "123", + }, + ) + require.NoError(t, err) + fakeIntegrationClt := fakeIntegrationClt + fakeIntegrationClt.getIntegrationFn = func(context.Context, string) (types.Integration, error) { + return azureIntegration, nil + } + _, err = provider.GetConfig(ctx, dummyRegion, + WithCredentialsMaybeIntegration(dummyIntegration), + WithOIDCIntegrationClient(&fakeIntegrationClt), + WithSTSClientProvider(stsClt), + ) + require.Error(t, err) + require.ErrorContains(t, err, "invalid integration subkind") + }) + + t.Run("with an integration client, must return token generation errors", func(t *testing.T) { + ctx := context.Background() + fakeIntegrationClt := fakeIntegrationClt + fakeIntegrationClt.getTokenFn = func(context.Context, string) (string, error) { + return "", trace.BadParameter("failed to generate OIDC token") + } + _, err = provider.GetConfig(ctx, dummyRegion, + WithCredentialsMaybeIntegration(dummyIntegration), + WithOIDCIntegrationClient(&fakeIntegrationClt), + WithSTSClientProvider(stsClt), + ) + require.Error(t, err) + require.ErrorContains(t, err, "failed to generate OIDC token") }) - t.Run("with an integration credential provider, must return the credentials", func(t *testing.T) { + t.Run("with an integration client, must return the credentials", func(t *testing.T) { ctx := context.Background() cfg, err := provider.GetConfig(ctx, dummyRegion, WithCredentialsMaybeIntegration(dummyIntegration), - WithIntegrationCredentialProvider(func(ctx context.Context, region, integration string) (aws.CredentialsProvider, error) { - if region == dummyRegion && integration == dummyIntegration { - return &mockCredentialProvider{ - cred: aws.Credentials{ - SessionToken: "foo-bar", - }, - }, nil - } - return nil, trace.NotFound("no creds in region %q with integration %q", region, integration) - })) + WithOIDCIntegrationClient(&fakeIntegrationClt), + WithSTSClientProvider(stsClt), + ) require.NoError(t, err) creds, err := cfg.Credentials.Retrieve(ctx) require.NoError(t, err) - require.Equal(t, "foo-bar", creds.SessionToken) + require.Equal(t, "oidc-token", creds.SecretAccessKey) }) t.Run("with an integration credential provider assuming a role, must return assumed role credentials", func(t *testing.T) { @@ -119,23 +192,9 @@ func testGetConfigIntegration(t *testing.T, provider Provider) { cfg, err := provider.GetConfig(ctx, dummyRegion, WithCredentialsMaybeIntegration(dummyIntegration), - WithIntegrationCredentialProvider(func(ctx context.Context, region, integration string) (aws.CredentialsProvider, error) { - if region == dummyRegion && integration == dummyIntegration { - return &mockCredentialProvider{ - cred: aws.Credentials{ - SessionToken: "foo-bar", - }, - }, nil - } - return nil, trace.NotFound("no creds in region %q with integration %q", region, integration) - }), + WithOIDCIntegrationClient(&fakeIntegrationClt), WithAssumeRole("roleA", "abc123"), - WithAssumeRoleClientProviderFunc(func(cfg aws.Config) stscreds.AssumeRoleAPIClient { - creds, err := cfg.Credentials.Retrieve(context.Background()) - require.NoError(t, err) - require.Equal(t, "foo-bar", creds.SessionToken) - return &mockAssumeRoleAPIClient{} - }), + WithSTSClientProvider(stsClt), ) require.NoError(t, err) creds, err := cfg.Credentials.Retrieve(ctx) @@ -148,25 +207,11 @@ func testGetConfigIntegration(t *testing.T, provider Provider) { ctx := context.Background() _, err := provider.GetConfig(ctx, dummyRegion, WithCredentialsMaybeIntegration(dummyIntegration), - WithIntegrationCredentialProvider(func(ctx context.Context, region, integration string) (aws.CredentialsProvider, error) { - if region == dummyRegion && integration == dummyIntegration { - return &mockCredentialProvider{ - cred: aws.Credentials{ - SessionToken: "foo-bar", - }, - }, nil - } - return nil, trace.NotFound("no creds in region %q with integration %q", region, integration) - }), + WithOIDCIntegrationClient(&fakeIntegrationClt), WithAssumeRole("roleA", "abc123"), WithAssumeRole("roleB", "abc123"), WithAssumeRole("roleC", "abc123"), - WithAssumeRoleClientProviderFunc(func(cfg aws.Config) stscreds.AssumeRoleAPIClient { - creds, err := cfg.Credentials.Retrieve(context.Background()) - require.NoError(t, err) - require.Equal(t, "foo-bar", creds.SessionToken) - return &mockAssumeRoleAPIClient{} - }), + WithSTSClientProvider(stsClt), ) require.Error(t, err) require.ErrorContains(t, err, "role chain contains more than 2 roles") @@ -177,10 +222,8 @@ func testGetConfigIntegration(t *testing.T, provider Provider) { _, err := provider.GetConfig(ctx, dummyRegion, WithCredentialsMaybeIntegration(""), - WithIntegrationCredentialProvider(func(ctx context.Context, region, integration string) (aws.CredentialsProvider, error) { - require.Fail(t, "this function should not be called") - return nil, nil - })) + WithOIDCIntegrationClient(&fakeOIDCIntegrationClient{unauth: true}), + ) require.NoError(t, err) }) @@ -189,10 +232,8 @@ func testGetConfigIntegration(t *testing.T, provider Provider) { _, err := provider.GetConfig(ctx, dummyRegion, WithAmbientCredentials(), - WithIntegrationCredentialProvider(func(ctx context.Context, region, integration string) (aws.CredentialsProvider, error) { - require.Fail(t, "this function should not be called") - return nil, nil - })) + WithOIDCIntegrationClient(&fakeOIDCIntegrationClient{unauth: true}), + ) require.NoError(t, err) }) @@ -200,10 +241,8 @@ func testGetConfigIntegration(t *testing.T, provider Provider) { ctx := context.Background() _, err := provider.GetConfig(ctx, dummyRegion, - WithIntegrationCredentialProvider(func(ctx context.Context, region, integration string) (aws.CredentialsProvider, error) { - require.Fail(t, "this function should not be called") - return nil, nil - })) + WithOIDCIntegrationClient(&fakeOIDCIntegrationClient{unauth: true}), + ) require.Error(t, err) require.ErrorContains(t, err, "missing credentials source") }) @@ -221,3 +260,24 @@ func TestNewCacheKey(t *testing.T) { `) require.Equal(t, want, got) } + +type fakeOIDCIntegrationClient struct { + unauth bool + + getIntegrationFn func(context.Context, string) (types.Integration, error) + getTokenFn func(context.Context, string) (string, error) +} + +func (f *fakeOIDCIntegrationClient) GetIntegration(ctx context.Context, name string) (types.Integration, error) { + if f.unauth { + return nil, trace.AccessDenied("unauthorized") + } + return f.getIntegrationFn(ctx, name) +} + +func (f *fakeOIDCIntegrationClient) GenerateAWSOIDCToken(ctx context.Context, integrationName string) (string, error) { + if f.unauth { + return "", trace.AccessDenied("unauthorized") + } + return f.getTokenFn(ctx, integrationName) +} diff --git a/lib/cloud/awsconfig/cache.go b/lib/cloud/awsconfig/cache.go index 3d664ba04c350..cdb315703212a 100644 --- a/lib/cloud/awsconfig/cache.go +++ b/lib/cloud/awsconfig/cache.go @@ -36,10 +36,23 @@ func awsCredentialsCacheOptions(opts *aws.CredentialsCacheOptions) { // role. type Cache struct { awsConfigCache *utils.FnCache + defaultOptions []OptionsFn +} + +// CacheOption is an option func for setting additional options when creating +// a new config cache. +type CacheOption func(*Cache) + +// WithDefaults is a [CacheOption] function that sets default [OptionsFn] to +// use when getting AWS config. +func WithDefaults(optFns ...OptionsFn) CacheOption { + return func(c *Cache) { + c.defaultOptions = optFns + } } // NewCache returns a new [Cache]. -func NewCache() (*Cache, error) { +func NewCache(optFns ...CacheOption) (*Cache, error) { c, err := utils.NewFnCache(utils.FnCacheConfig{ TTL: 15 * time.Minute, ReloadOnErr: true, @@ -47,14 +60,27 @@ func NewCache() (*Cache, error) { if err != nil { return nil, trace.Wrap(err) } - return &Cache{ + cache := &Cache{ awsConfigCache: c, - }, nil + } + for _, fn := range optFns { + fn(cache) + } + return cache, nil +} + +// withDefaultOptions prepends default options to the given option funcs, +// providing for default cache options and per-call options. +func (c *Cache) withDefaultOptions(optFns []OptionsFn) []OptionsFn { + if c.defaultOptions != nil { + return append(c.defaultOptions, optFns...) + } + return optFns } // GetConfig returns an [aws.Config] for the given region and options. func (c *Cache) GetConfig(ctx context.Context, region string, optFns ...OptionsFn) (aws.Config, error) { - opts, err := buildOptions(optFns...) + opts, err := buildOptions(c.withDefaultOptions(optFns)...) if err != nil { return aws.Config{}, trace.Wrap(err) } @@ -112,7 +138,7 @@ func (c *Cache) getConfigForRoleChain(ctx context.Context, cfg aws.Config, opts } credProvider, err := utils.FnCacheGet(ctx, c.awsConfigCache, cacheKey, func(ctx context.Context) (aws.CredentialsProvider, error) { - clt := opts.assumeRoleClientProvider(cfg) + clt := opts.stsClientProvider(cfg) credProvider := getAssumeRoleProvider(ctx, clt, r) cc := aws.NewCredentialsCache(credProvider, awsCredentialsCacheOptions, diff --git a/lib/cloud/mocks/aws_config.go b/lib/cloud/mocks/aws_config.go index 7edadf80a9e20..b52dfbd36d74a 100644 --- a/lib/cloud/mocks/aws_config.go +++ b/lib/cloud/mocks/aws_config.go @@ -22,12 +22,15 @@ import ( "context" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/gravitational/trace" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/cloud/awsconfig" ) type AWSConfigProvider struct { - STSClient *STSClient + STSClient *STSClient + OIDCIntegrationClient awsconfig.OIDCIntegrationClient } func (f *AWSConfigProvider) GetConfig(ctx context.Context, region string, optFns ...awsconfig.OptionsFn) (aws.Config, error) { @@ -35,8 +38,32 @@ func (f *AWSConfigProvider) GetConfig(ctx context.Context, region string, optFns if stsClt == nil { stsClt = &STSClient{} } - optFns = append(optFns, awsconfig.WithAssumeRoleClientProviderFunc( - newAssumeRoleClientProviderFunc(stsClt), - )) + optFns = append(optFns, + awsconfig.WithOIDCIntegrationClient(f.OIDCIntegrationClient), + awsconfig.WithSTSClientProvider( + newAssumeRoleClientProviderFunc(stsClt), + ), + ) return awsconfig.GetConfig(ctx, region, optFns...) } + +type FakeOIDCIntegrationClient struct { + Unauth bool + + Integration types.Integration + Token string +} + +func (f *FakeOIDCIntegrationClient) GetIntegration(ctx context.Context, name string) (types.Integration, error) { + if f.Unauth { + return nil, trace.AccessDenied("unauthorized") + } + return f.Integration, nil +} + +func (f *FakeOIDCIntegrationClient) GenerateAWSOIDCToken(ctx context.Context, integrationName string) (string, error) { + if f.Unauth { + return "", trace.AccessDenied("unauthorized") + } + return f.Token, nil +} diff --git a/lib/cloud/mocks/aws_sts.go b/lib/cloud/mocks/aws_sts.go index 713de480ebf86..178a1259669a4 100644 --- a/lib/cloud/mocks/aws_sts.go +++ b/lib/cloud/mocks/aws_sts.go @@ -54,7 +54,20 @@ type STSClient struct { recordFn func(roleARN, externalID string) } -func (m *STSClient) AssumeRole(ctx context.Context, in *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { +func (m *STSClient) AssumeRoleWithWebIdentity(ctx context.Context, in *sts.AssumeRoleWithWebIdentityInput, _ ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) { + m.record(aws.ToString(in.RoleArn), "") + expiry := time.Now().Add(60 * time.Minute) + return &sts.AssumeRoleWithWebIdentityOutput{ + Credentials: &ststypes.Credentials{ + AccessKeyId: in.RoleArn, + SecretAccessKey: aws.String("secret"), + SessionToken: aws.String("token"), + Expiration: &expiry, + }, + }, nil +} + +func (m *STSClient) AssumeRole(ctx context.Context, in *sts.AssumeRoleInput, _ ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { // Retrieve credentials if we have a credential provider, so that all // assume-role providers in a role chain are triggered to call AssumeRole. if m.credentialProvider != nil { @@ -93,8 +106,8 @@ func (m *STSClient) record(roleARN, externalID string) { } } -func newAssumeRoleClientProviderFunc(base *STSClient) awsconfig.AssumeRoleClientProviderFunc { - return func(cfg aws.Config) stscreds.AssumeRoleAPIClient { +func newAssumeRoleClientProviderFunc(base *STSClient) awsconfig.STSClientProviderFunc { + return func(cfg aws.Config) awsconfig.STSClient { if cfg.Credentials != nil { if _, ok := cfg.Credentials.(*stscreds.AssumeRoleProvider); ok { // Create a new fake client linked to the old one. diff --git a/lib/config/configuration.go b/lib/config/configuration.go index 546a48700d4fe..dda2ac6859cf4 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -27,7 +27,6 @@ import ( "crypto/x509" "errors" "io" - "io/fs" "log/slog" "maps" "net" @@ -72,13 +71,6 @@ import ( logutils "github.com/gravitational/teleport/lib/utils/log" ) -const ( - // logFileDefaultMode is the preferred permissions mode for log file. - logFileDefaultMode fs.FileMode = 0o644 - // logFileDefaultFlag is the preferred flags set to log file. - logFileDefaultFlag = os.O_WRONLY | os.O_CREATE | os.O_APPEND -) - // CommandLineFlags stores command line flag values, it's a much simplified subset // of Teleport configuration (which is fully expressed via YAML config file) type CommandLineFlags struct { @@ -789,79 +781,22 @@ func applyAuthOrProxyAddress(fc *FileConfig, cfg *servicecfg.Config) error { } func applyLogConfig(loggerConfig Log, cfg *servicecfg.Config) error { - // TODO: this code is copied in the access plugin logging setup `logger.Config.NewSLogLogger` - // We'll want to deduplicate the logic next time we refactor the logging setup - var w io.Writer switch loggerConfig.Output { - case "": - w = os.Stderr - case "stderr", "error", "2": - w = os.Stderr - cfg.Console = io.Discard // disable console printing - case "stdout", "out", "1": - w = os.Stdout + case "stderr", "error", "2", "stdout", "out", "1": cfg.Console = io.Discard // disable console printing - case teleport.Syslog: - var err error - w, err = utils.NewSyslogWriter() - if err != nil { - slog.ErrorContext(context.Background(), "Failed to switch logging to syslog", "error", err) - break - } - default: - // Assume this is a file path. - sharedWriter, err := logutils.NewFileSharedWriter(loggerConfig.Output, logFileDefaultFlag, logFileDefaultMode) - if err != nil { - return trace.Wrap(err, "failed to init the log file shared writer") - } - w = logutils.NewWriterFinalizer[*logutils.FileSharedWriter](sharedWriter) - if err := sharedWriter.RunWatcherReopen(context.Background()); err != nil { - return trace.Wrap(err) - } - } - - level := new(slog.LevelVar) - switch strings.ToLower(loggerConfig.Severity) { - case "", "info": - level.Set(slog.LevelInfo) - case "err", "error": - level.Set(slog.LevelError) - case teleport.DebugLevel: - level.Set(slog.LevelDebug) - case "warn", "warning": - level.Set(slog.LevelWarn) - case "trace": - level.Set(logutils.TraceLevel) - default: - return trace.BadParameter("unsupported logger severity: %q", loggerConfig.Severity) } - configuredFields, err := logutils.ValidateFields(loggerConfig.Format.ExtraFields) + logger, level, err := logutils.Initialize(logutils.Config{ + Output: loggerConfig.Output, + Severity: loggerConfig.Severity, + Format: loggerConfig.Format.Output, + ExtraFields: loggerConfig.Format.ExtraFields, + EnableColors: utils.IsTerminal(os.Stderr), + }) if err != nil { return trace.Wrap(err) } - var logger *slog.Logger - switch strings.ToLower(loggerConfig.Format.Output) { - case "": - fallthrough // not set. defaults to 'text' - case "text": - logger = slog.New(logutils.NewSlogTextHandler(w, logutils.SlogTextHandlerConfig{ - Level: level, - EnableColors: utils.IsTerminal(os.Stderr), - ConfiguredFields: configuredFields, - })) - slog.SetDefault(logger) - case "json": - logger = slog.New(logutils.NewSlogJSONHandler(w, logutils.SlogJSONHandlerConfig{ - Level: level, - ConfiguredFields: configuredFields, - })) - slog.SetDefault(logger) - default: - return trace.BadParameter("unsupported log output format : %q", loggerConfig.Format.Output) - } - cfg.Logger = logger cfg.LoggerLevel = level return nil diff --git a/lib/integrations/awsoidc/clientsv1.go b/lib/integrations/awsoidc/clientsv1.go index 8c16f4c66156a..ae2e0be6a186b 100644 --- a/lib/integrations/awsoidc/clientsv1.go +++ b/lib/integrations/awsoidc/clientsv1.go @@ -44,9 +44,6 @@ type IntegrationTokenGenerator interface { // GetIntegration returns the specified integration resources. GetIntegration(ctx context.Context, name string) (types.Integration, error) - // GetProxies returns a list of registered proxies. - GetProxies() ([]types.Server, error) - // GenerateAWSOIDCToken generates a token to be used to execute an AWS OIDC Integration action. GenerateAWSOIDCToken(ctx context.Context, integration string) (string, error) } diff --git a/lib/integrations/awsoidc/listdeployeddatabaseservice.go b/lib/integrations/awsoidc/listdeployeddatabaseservice.go index c2894902f78fe..ad5bb9606faf4 100644 --- a/lib/integrations/awsoidc/listdeployeddatabaseservice.go +++ b/lib/integrations/awsoidc/listdeployeddatabaseservice.go @@ -27,6 +27,7 @@ import ( ecstypes "github.com/aws/aws-sdk-go-v2/service/ecs/types" "github.com/gravitational/trace" + awslib "github.com/gravitational/teleport/lib/cloud/aws" "github.com/gravitational/teleport/lib/integrations/awsoidc/tags" ) @@ -139,6 +140,11 @@ func ListDeployedDatabaseServices(ctx context.Context, clt ListDeployedDatabaseS listServicesOutput, err := clt.ListServices(ctx, listServicesInput) if err != nil { + convertedError := awslib.ConvertRequestFailureErrorV2(err) + if trace.IsNotFound(convertedError) { + return &ListDeployedDatabaseServicesResponse{}, nil + } + return nil, trace.Wrap(err) } diff --git a/lib/integrations/awsoidc/listdeployeddatabaseservice_test.go b/lib/integrations/awsoidc/listdeployeddatabaseservice_test.go index 67f332d495c2b..84b163d519465 100644 --- a/lib/integrations/awsoidc/listdeployeddatabaseservice_test.go +++ b/lib/integrations/awsoidc/listdeployeddatabaseservice_test.go @@ -110,11 +110,11 @@ type mockListECSClient struct { } func (m *mockListECSClient) ListServices(ctx context.Context, params *ecs.ListServicesInput, optFns ...func(*ecs.Options)) (*ecs.ListServicesOutput, error) { - ret := &ecs.ListServicesOutput{} - if aws.ToString(params.Cluster) != m.clusterName { - return ret, nil + if aws.ToString(params.Cluster) != m.clusterName || len(m.services) == 0 { + return nil, trace.NotFound("ECS Cluster not found") } + ret := &ecs.ListServicesOutput{} requestedPage := 1 totalEndpoints := len(m.services) @@ -348,6 +348,25 @@ func TestListDeployedDatabaseServices(t *testing.T) { }, errCheck: require.NoError, }, + { + name: "returns empty list when the ECS Cluster does not exist", + req: ListDeployedDatabaseServicesRequest{ + Integration: "my-integration", + TeleportClusterName: "my-cluster", + Region: "us-east-1", + }, + mockClient: func() *mockListECSClient { + ret := &mockListECSClient{ + pageSize: 10, + } + return ret + }, + respCheck: func(t *testing.T, resp *ListDeployedDatabaseServicesResponse) { + require.Empty(t, resp.DeployedDatabaseServices, "expected 0 services") + require.Empty(t, resp.NextToken, "expected an empty NextToken") + }, + errCheck: require.NoError, + }, } { t.Run(tt.name, func(t *testing.T) { resp, err := ListDeployedDatabaseServices(ctx, tt.mockClient(), tt.req) diff --git a/lib/service/service.go b/lib/service/service.go index 51d171f3737b3..7638ee5e85caf 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -56,7 +56,6 @@ import ( "github.com/jonboulle/clockwork" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/quic-go/quic-go" - "github.com/sirupsen/logrus" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/otel/attribute" "golang.org/x/crypto/acme" @@ -992,10 +991,6 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { } processID := fmt.Sprintf("%v", nextProcessID()) - cfg.Log = utils.WrapLogger(cfg.Log.WithFields(logrus.Fields{ - teleport.ComponentKey: teleport.Component(teleport.ComponentProcess, processID), - "pid": fmt.Sprintf("%v.%v", os.Getpid(), processID), - })) cfg.Logger = cfg.Logger.With( teleport.ComponentKey, teleport.Component(teleport.ComponentProcess, processID), "pid", fmt.Sprintf("%v.%v", os.Getpid(), processID), diff --git a/lib/service/service_test.go b/lib/service/service_test.go index 16309ed59ac72..52e59387ff580 100644 --- a/lib/service/service_test.go +++ b/lib/service/service_test.go @@ -948,7 +948,6 @@ func TestTeleportProcess_reconnectToAuth(t *testing.T) { cfg.Testing.ConnectFailureC = make(chan time.Duration, 5) cfg.Testing.ClientTimeout = time.Millisecond cfg.InstanceMetadataClient = imds.NewDisabledIMDSClient() - cfg.Log = utils.NewLoggerForTests() cfg.Logger = utils.NewSlogLoggerForTests() process, err := NewTeleport(cfg) require.NoError(t, err) @@ -1829,15 +1828,22 @@ func TestInitDatabaseService(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() + // Arbitrary channel size to avoid blocking. + // We should not receive more than 1024 events as we have less than 1024 services. + serviceExitedEvents := make(chan Event, 1024) + var eg errgroup.Group process, err := NewTeleport(cfg) require.NoError(t, err) + process.ListenForEvents(ctx, ServiceExitedWithErrorEvent, serviceExitedEvents) require.NoError(t, process.Start()) eg.Go(func() error { return process.WaitForSignals(ctx, nil) }) // Ensures the process is closed in failure scenarios. t.Cleanup(func() { cancel() _ = eg.Wait() + _ = process.Close() + require.NoError(t, process.Wait()) }) if !test.expectErr { @@ -1846,15 +1852,24 @@ func TestInitDatabaseService(t *testing.T) { require.NoError(t, process.Close()) // Expect Teleport to shutdown without reporting any issue. require.NoError(t, eg.Wait()) + require.NoError(t, process.Wait()) return } - event, err := process.WaitForEvent(ctx, ServiceExitedWithErrorEvent) - require.NoError(t, err) - require.NotNil(t, event) - exitPayload, ok := event.Payload.(ExitEventPayload) - require.True(t, ok, "expected ExitEventPayload but got %T", event.Payload) - require.Equal(t, "db.init", exitPayload.Service.Name()) + // The first service to exit should be the db one, with a "db.init" event. + // We can't use WaitForEvents because it only returns the last event for this type. + // As the test causes Teleport to crash, other services might exit in error before + // we get the event, causing the test to fail. + select { + case event := <-serviceExitedEvents: + require.NotNil(t, event) + exitPayload, ok := event.Payload.(ExitEventPayload) + require.True(t, ok, "expected ExitEventPayload but got %T", event.Payload) + require.Equal(t, "db.init", exitPayload.Service.Name(), "expected db init failure, got instead %q with error %q", exitPayload.Service.Name(), exitPayload.Error) + case <-ctx.Done(): + require.Fail(t, "context timed out, we never received the failed db.init event") + } + // Database service init is a critical service, meaning failures on // it should cause the process to exit with error. require.Error(t, eg.Wait()) diff --git a/lib/service/servicecfg/config.go b/lib/service/servicecfg/config.go index 6a14f1ceba5d0..a89e79a8f6302 100644 --- a/lib/service/servicecfg/config.go +++ b/lib/service/servicecfg/config.go @@ -34,7 +34,6 @@ import ( "github.com/ghodss/yaml" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" @@ -52,7 +51,6 @@ import ( "github.com/gravitational/teleport/lib/sshca" usagereporter "github.com/gravitational/teleport/lib/usagereporter/teleport" "github.com/gravitational/teleport/lib/utils" - logutils "github.com/gravitational/teleport/lib/utils/log" ) // Config contains the configuration for all services that Teleport can run. @@ -223,10 +221,6 @@ type Config struct { // Kube is a Kubernetes API gateway using Teleport client identities. Kube KubeConfig - // Log optionally specifies the logger. - // Deprecated: use Logger instead. - Log utils.Logger - // Logger outputs messages using slog. The underlying handler respects // the user supplied logging config. Logger *slog.Logger @@ -518,10 +512,6 @@ func ApplyDefaults(cfg *Config) { cfg.Version = defaults.TeleportConfigVersionV1 - if cfg.Log == nil { - cfg.Log = utils.NewLogger() - } - if cfg.Logger == nil { cfg.Logger = slog.Default() } @@ -698,10 +688,6 @@ func applyDefaults(cfg *Config) { cfg.Console = io.Discard } - if cfg.Log == nil { - cfg.Log = logrus.StandardLogger() - } - if cfg.Logger == nil { cfg.Logger = slog.Default() } @@ -799,7 +785,6 @@ func verifyEnabledService(cfg *Config) error { // If called after `config.ApplyFileConfig` or `config.Configure` it will also // change the global loggers. func (c *Config) SetLogLevel(level slog.Level) { - c.Log.SetLevel(logutils.SlogLevelToLogrusLevel(level)) c.LoggerLevel.Set(level) } diff --git a/lib/service/servicecfg/config_test.go b/lib/service/servicecfg/config_test.go index e9a6be2df4056..8ed785c0998f9 100644 --- a/lib/service/servicecfg/config_test.go +++ b/lib/service/servicecfg/config_test.go @@ -28,7 +28,6 @@ import ( "testing" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/types" @@ -651,44 +650,34 @@ func TestWebPublicAddr(t *testing.T) { func TestSetLogLevel(t *testing.T) { for _, test := range []struct { - logLevel slog.Level - expectedLogrusLevel logrus.Level + logLevel slog.Level }{ { - logLevel: logutils.TraceLevel, - expectedLogrusLevel: logrus.TraceLevel, + logLevel: logutils.TraceLevel, }, { - logLevel: slog.LevelDebug, - expectedLogrusLevel: logrus.DebugLevel, + logLevel: slog.LevelDebug, }, { - logLevel: slog.LevelInfo, - expectedLogrusLevel: logrus.InfoLevel, + logLevel: slog.LevelInfo, }, { - logLevel: slog.LevelWarn, - expectedLogrusLevel: logrus.WarnLevel, + logLevel: slog.LevelWarn, }, { - logLevel: slog.LevelError, - expectedLogrusLevel: logrus.ErrorLevel, + logLevel: slog.LevelError, }, } { t.Run(test.logLevel.String(), func(t *testing.T) { // Create a configuration with local loggers to avoid modifying the // global instances. c := &Config{ - Log: logrus.New(), Logger: slog.New(logutils.NewSlogTextHandler(io.Discard, logutils.SlogTextHandlerConfig{})), } ApplyDefaults(c) c.SetLogLevel(test.logLevel) require.Equal(t, test.logLevel, c.LoggerLevel.Level()) - require.IsType(t, &logrus.Logger{}, c.Log) - l, _ := c.Log.(*logrus.Logger) - require.Equal(t, test.expectedLogrusLevel, l.GetLevel()) }) } } diff --git a/lib/services/reconciler.go b/lib/services/reconciler.go index 17b136a056152..9aa23cac0007f 100644 --- a/lib/services/reconciler.go +++ b/lib/services/reconciler.go @@ -55,6 +55,11 @@ type GenericReconcilerConfig[K comparable, T any] struct { OnDelete func(context.Context, T) error // Logger emits log messages. Logger *slog.Logger + // AllowOriginChanges is a flag that allows the reconciler to change the + // origin value of a reconciled resource. By default, origin changes are + // disallowed to enforce segregation between of resources from different + // sources. + AllowOriginChanges bool } // CheckAndSetDefaults validates the reconciler configuration and sets defaults. @@ -177,18 +182,21 @@ func (r *GenericReconciler[K, T]) processNewResource(ctx context.Context, curren return nil } - // Don't overwrite resource of a different origin (e.g., keep static resource from config and ignore dynamic resource) - registeredOrigin, err := types.GetOrigin(registered) - if err != nil { - return trace.Wrap(err) - } - newOrigin, err := types.GetOrigin(newT) - if err != nil { - return trace.Wrap(err) - } - if registeredOrigin != newOrigin { - r.logger.WarnContext(ctx, "New resource has different origin, not updating", "name", key, "new_origin", newOrigin, "existing_origin", registeredOrigin) - return nil + if !r.cfg.AllowOriginChanges { + // Don't overwrite resource of a different origin (e.g., keep static resource from config and ignore dynamic resource) + registeredOrigin, err := types.GetOrigin(registered) + if err != nil { + return trace.Wrap(err) + } + newOrigin, err := types.GetOrigin(newT) + if err != nil { + return trace.Wrap(err) + } + if registeredOrigin != newOrigin { + r.logger.WarnContext(ctx, "New resource has different origin, not updating", + "name", key, "new_origin", newOrigin, "existing_origin", registeredOrigin) + return nil + } } // If the resource is already registered but was updated, see if its diff --git a/lib/services/reconciler_test.go b/lib/services/reconciler_test.go index 37ca13e1447a0..ad97fb61c904d 100644 --- a/lib/services/reconciler_test.go +++ b/lib/services/reconciler_test.go @@ -43,6 +43,7 @@ func TestReconciler(t *testing.T) { onCreateCalls []testResource onUpdateCalls []updateCall onDeleteCalls []testResource + configure func(cfg *ReconcilerConfig[testResource]) comparator func(testResource, testResource) int }{ { @@ -73,13 +74,30 @@ func TestReconciler(t *testing.T) { }, }, { - description: "resources with different origins don't overwrite each other", + description: "resources with different origins don't overwrite each other by default", selectors: []ResourceMatcher{{ Labels: types.Labels{"*": []string{"*"}}, }}, registeredResources: []testResource{makeStaticResource("res1", nil)}, newResources: []testResource{makeDynamicResource("res1", nil)}, }, + { + description: "resources with different origins overwrite each other when allowed", + selectors: []ResourceMatcher{{ + Labels: types.Labels{"*": []string{"*"}}, + }}, + configure: func(cfg *ReconcilerConfig[testResource]) { + cfg.AllowOriginChanges = true + }, + registeredResources: []testResource{makeStaticResource("res1", nil)}, + newResources: []testResource{makeDynamicResource("res1", nil)}, + onUpdateCalls: []updateCall{ + { + old: makeStaticResource("res1", nil), + new: makeDynamicResource("res1", nil), + }, + }, + }, { description: "resource that's no longer present should be removed", selectors: []ResourceMatcher{{ @@ -198,7 +216,7 @@ func TestReconciler(t *testing.T) { var onCreateCalls, onDeleteCalls []testResource var onUpdateCalls []updateCall - reconciler, err := NewReconciler[testResource](ReconcilerConfig[testResource]{ + cfg := ReconcilerConfig[testResource]{ Matcher: func(tr testResource) bool { return MatchResourceLabels(test.selectors, tr.GetMetadata().Labels) }, @@ -225,7 +243,13 @@ func TestReconciler(t *testing.T) { onDeleteCalls = append(onDeleteCalls, tr) return nil }, - }) + } + + if test.configure != nil { + test.configure(&cfg) + } + + reconciler, err := NewReconciler[testResource](cfg) require.NoError(t, err) // Reconcile and make sure we got all expected callback calls. diff --git a/lib/srv/discovery/discovery.go b/lib/srv/discovery/discovery.go index 28690130d51a7..f37ba025d2450 100644 --- a/lib/srv/discovery/discovery.go +++ b/lib/srv/discovery/discovery.go @@ -224,7 +224,11 @@ kubernetes matchers are present.`) c.CloudClients = cloudClients } if c.AWSConfigProvider == nil { - provider, err := awsconfig.NewCache() + provider, err := awsconfig.NewCache( + awsconfig.WithDefaults( + awsconfig.WithOIDCIntegrationClient(c.AccessPoint), + ), + ) if err != nil { return trace.Wrap(err, "unable to create AWS config provider cache") } @@ -232,9 +236,8 @@ kubernetes matchers are present.`) } if c.AWSDatabaseFetcherFactory == nil { factory, err := db.NewAWSFetcherFactory(db.AWSFetcherFactoryConfig{ - CloudClients: c.CloudClients, - AWSConfigProvider: c.AWSConfigProvider, - IntegrationCredentialProviderFn: c.getIntegrationCredentialProviderFn(), + CloudClients: c.CloudClients, + AWSConfigProvider: c.AWSConfigProvider, }) if err != nil { return trace.Wrap(err) @@ -312,33 +315,10 @@ kubernetes matchers are present.`) } func (c *Config) getAWSConfig(ctx context.Context, region string, opts ...awsconfig.OptionsFn) (aws.Config, error) { - opts = append(opts, awsconfig.WithIntegrationCredentialProvider(c.getIntegrationCredentialProviderFn())) cfg, err := c.AWSConfigProvider.GetConfig(ctx, region, opts...) return cfg, trace.Wrap(err) } -func (c *Config) getIntegrationCredentialProviderFn() awsconfig.IntegrationCredentialProviderFunc { - return func(ctx context.Context, region, integrationName string) (aws.CredentialsProvider, error) { - integration, err := c.AccessPoint.GetIntegration(ctx, integrationName) - if err != nil { - return nil, trace.Wrap(err) - } - if integration.GetAWSOIDCIntegrationSpec() == nil { - return nil, trace.BadParameter("integration does not have aws oidc spec fields %q", integrationName) - } - token, err := c.AccessPoint.GenerateAWSOIDCToken(ctx, integrationName) - if err != nil { - return nil, trace.Wrap(err) - } - cred, err := awsoidc.NewAWSCredentialsProvider(ctx, &awsoidc.AWSClientRequest{ - Token: token, - RoleARN: integration.GetAWSOIDCIntegrationSpec().RoleARN, - Region: region, - }) - return cred, trace.Wrap(err) - } -} - // Server is a discovery server, used to discover cloud resources for // inclusion in Teleport type Server struct { diff --git a/lib/srv/discovery/discovery_test.go b/lib/srv/discovery/discovery_test.go index f3c387a475932..865517ba4c33c 100644 --- a/lib/srv/discovery/discovery_test.go +++ b/lib/srv/discovery/discovery_test.go @@ -37,7 +37,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis/v3" awsv2 "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/ec2" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/aws/aws-sdk-go-v2/service/redshift" @@ -2032,18 +2031,6 @@ func TestDiscoveryDatabase(t *testing.T) { Clusters: []*eks.Cluster{eksAWSResource}, }, } - fakeConfigProvider := &mocks.AWSConfigProvider{} - dbFetcherFactory, err := db.NewAWSFetcherFactory(db.AWSFetcherFactoryConfig{ - AWSConfigProvider: fakeConfigProvider, - CloudClients: testCloudClients, - IntegrationCredentialProviderFn: func(_ context.Context, _, _ string) (awsv2.CredentialsProvider, error) { - return credentials.NewStaticCredentialsProvider("key", "secret", "session"), nil - }, - RedshiftClientProviderFn: newFakeRedshiftClientProvider(&mocks.RedshiftClient{ - Clusters: []redshifttypes.Cluster{*awsRedshiftResource}, - }), - }) - require.NoError(t, err) tcs := []struct { name string @@ -2334,6 +2321,23 @@ func TestDiscoveryDatabase(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { require.NoError(t, tlsServer.Close()) }) + awsOIDCIntegration, err := types.NewIntegrationAWSOIDC(types.Metadata{ + Name: integrationName, + }, &types.AWSOIDCIntegrationSpecV1{ + RoleARN: "arn:aws:iam::123456789012:role/teleport", + }) + require.NoError(t, err) + + testAuthServer.AuthServer.IntegrationsTokenGenerator = &mockIntegrationsTokenGenerator{ + proxies: nil, + integrations: map[string]types.Integration{ + awsOIDCIntegration.GetName(): awsOIDCIntegration, + }, + } + + _, err = tlsServer.Auth().CreateIntegration(ctx, awsOIDCIntegration) + require.NoError(t, err) + // Auth client for discovery service. identity := auth.TestServerID(types.RoleDiscovery, "hostID") authClient, err := tlsServer.NewClient(identity) @@ -2349,6 +2353,19 @@ func TestDiscoveryDatabase(t *testing.T) { waitForReconcile := make(chan struct{}) reporter := &mockUsageReporter{} tlsServer.Auth().SetUsageReporter(reporter) + accessPoint := getDiscoveryAccessPoint(tlsServer.Auth(), authClient) + fakeConfigProvider := &mocks.AWSConfigProvider{ + OIDCIntegrationClient: accessPoint, + } + dbFetcherFactory, err := db.NewAWSFetcherFactory(db.AWSFetcherFactoryConfig{ + AWSConfigProvider: fakeConfigProvider, + CloudClients: testCloudClients, + RedshiftClientProviderFn: newFakeRedshiftClientProvider(&mocks.RedshiftClient{ + Clusters: []redshifttypes.Cluster{*awsRedshiftResource}, + }), + }) + require.NoError(t, err) + srv, err := New( authz.ContextWithUser(ctx, identity.I), &Config{ @@ -2358,7 +2375,7 @@ func TestDiscoveryDatabase(t *testing.T) { AWSConfigProvider: fakeConfigProvider, ClusterFeatures: func() proto.Features { return proto.Features{} }, KubernetesClient: fake.NewSimpleClientset(), - AccessPoint: getDiscoveryAccessPoint(tlsServer.Auth(), authClient), + AccessPoint: accessPoint, Matchers: Matchers{ AWS: tc.awsMatchers, Azure: tc.azureMatchers, diff --git a/lib/srv/discovery/fetchers/db/aws.go b/lib/srv/discovery/fetchers/db/aws.go index f87e0e9a6c443..d6d70912d7092 100644 --- a/lib/srv/discovery/fetchers/db/aws.go +++ b/lib/srv/discovery/fetchers/db/aws.go @@ -55,9 +55,6 @@ type awsFetcherConfig struct { AWSClients cloud.AWSClients // AWSConfigProvider provides [aws.Config] for AWS SDK service clients. AWSConfigProvider awsconfig.Provider - // IntegrationCredentialProviderFn is a required function that provides - // credentials via AWS OIDC integration. - IntegrationCredentialProviderFn awsconfig.IntegrationCredentialProviderFunc // Type is the type of DB matcher, for example "rds", "redshift", etc. Type string // AssumeRole provides a role ARN and ExternalID to assume an AWS role diff --git a/lib/srv/discovery/fetchers/db/aws_redshift.go b/lib/srv/discovery/fetchers/db/aws_redshift.go index 508cb6e8810f1..0cda0b478e67b 100644 --- a/lib/srv/discovery/fetchers/db/aws_redshift.go +++ b/lib/srv/discovery/fetchers/db/aws_redshift.go @@ -53,7 +53,6 @@ func (f *redshiftPlugin) GetDatabases(ctx context.Context, cfg *awsFetcherConfig awsCfg, err := cfg.AWSConfigProvider.GetConfig(ctx, cfg.Region, awsconfig.WithAssumeRole(cfg.AssumeRole.RoleARN, cfg.AssumeRole.ExternalID), awsconfig.WithCredentialsMaybeIntegration(cfg.Integration), - awsconfig.WithIntegrationCredentialProvider(cfg.IntegrationCredentialProviderFn), ) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/srv/discovery/fetchers/db/db.go b/lib/srv/discovery/fetchers/db/db.go index 3ef56532d90af..8d79bc2bb65bc 100644 --- a/lib/srv/discovery/fetchers/db/db.go +++ b/lib/srv/discovery/fetchers/db/db.go @@ -73,9 +73,6 @@ type AWSFetcherFactoryConfig struct { AWSConfigProvider awsconfig.Provider // CloudClients is an interface for retrieving AWS SDK v1 cloud clients. CloudClients cloud.AWSClients - // IntegrationCredentialProviderFn is an optional function that provides - // credentials via AWS OIDC integration. - IntegrationCredentialProviderFn awsconfig.IntegrationCredentialProviderFunc // RedshiftClientProviderFn is an optional function that provides RedshiftClientProviderFn RedshiftClientProviderFunc } @@ -128,16 +125,15 @@ func (f *AWSFetcherFactory) MakeFetchers(ctx context.Context, matchers []types.A for _, makeFetcher := range makeFetchers { for _, region := range matcher.Regions { fetcher, err := makeFetcher(awsFetcherConfig{ - AWSClients: f.cfg.CloudClients, - Type: matcherType, - AssumeRole: assumeRole, - Labels: matcher.Tags, - Region: region, - Integration: matcher.Integration, - DiscoveryConfigName: discoveryConfigName, - AWSConfigProvider: f.cfg.AWSConfigProvider, - IntegrationCredentialProviderFn: f.cfg.IntegrationCredentialProviderFn, - redshiftClientProviderFn: f.cfg.RedshiftClientProviderFn, + AWSClients: f.cfg.CloudClients, + Type: matcherType, + AssumeRole: assumeRole, + Labels: matcher.Tags, + Region: region, + Integration: matcher.Integration, + DiscoveryConfigName: discoveryConfigName, + AWSConfigProvider: f.cfg.AWSConfigProvider, + redshiftClientProviderFn: f.cfg.RedshiftClientProviderFn, }) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/teleterm/cmd/db.go b/lib/teleterm/cmd/db.go index c18accc048b66..144d386224c38 100644 --- a/lib/teleterm/cmd/db.go +++ b/lib/teleterm/cmd/db.go @@ -71,6 +71,7 @@ func newDBCLICommandWithExecer(ctx context.Context, cluster *clusters.Cluster, g dbcmd.WithNoTLS(), dbcmd.WithTolerateMissingCLIClient(), dbcmd.WithExecer(execer), + dbcmd.WithOracleOpts(true /* can use TCP */, true /* has TCP servers */), dbcmd.WithGetDatabaseFunc(func(ctx context.Context, _ *client.TeleportClient, _ string) (types.Database, error) { getDatabaseOnce.Do(func() { database, getDatabaseError = cluster.GetDatabase(ctx, authClient, gateway.TargetURI()) diff --git a/lib/usertasks/descriptions.go b/lib/usertasks/descriptions.go index eb1655fee5ea7..3068e1d02b023 100644 --- a/lib/usertasks/descriptions.go +++ b/lib/usertasks/descriptions.go @@ -26,10 +26,7 @@ import ( //go:embed descriptions/*.md var descriptionsFS embed.FS -// DescriptionForDiscoverEC2Issue returns the description of the issue and fixing steps. -// The returned string contains a markdown document. -// If issue type is not recognized or doesn't have a specific description, them an empty string is returned. -func DescriptionForDiscoverEC2Issue(issueType string) string { +func loadIssueDescription(issueType string) string { filename := fmt.Sprintf("descriptions/%s.md", issueType) bs, err := descriptionsFS.ReadFile(filename) if err != nil { @@ -37,3 +34,17 @@ func DescriptionForDiscoverEC2Issue(issueType string) string { } return string(bs) } + +// DescriptionForDiscoverEC2Issue returns the description of the issue and fixing steps. +// The returned string contains a markdown document. +// If issue type is not recognized or doesn't have a specific description, them an empty string is returned. +func DescriptionForDiscoverEC2Issue(issueType string) string { + return loadIssueDescription(issueType) +} + +// DescriptionForDiscoverEKSIssue returns the description of the issue and fixing steps. +// The returned string contains a markdown document. +// If issue type is not recognized or doesn't have a specific description, them an empty string is returned. +func DescriptionForDiscoverEKSIssue(issueType string) string { + return loadIssueDescription(issueType) +} diff --git a/lib/usertasks/descriptions/eks-agent-not-connecting.md b/lib/usertasks/descriptions/eks-agent-not-connecting.md new file mode 100644 index 0000000000000..60d2a0b2c02a9 --- /dev/null +++ b/lib/usertasks/descriptions/eks-agent-not-connecting.md @@ -0,0 +1,8 @@ +The process of automatically enrolling EKS Clusters into Teleport, starts by installing the [`teleport-kube-agent`](https://goteleport.com/docs/reference/helm-reference/teleport-kube-agent/) to the cluster. + +If the installation is successful, the EKS Cluster will appear in your Resources list. + +However, the following EKS Clusters did not automatically enrolled. +This usually happens when the installation is taking too long or there was an error preventing the HELM chart installation. + +Open the Teleport Agent to get more information. \ No newline at end of file diff --git a/lib/usertasks/descriptions/eks-authentication-mode-unsupported.md b/lib/usertasks/descriptions/eks-authentication-mode-unsupported.md new file mode 100644 index 0000000000000..c6d3f55e569c8 --- /dev/null +++ b/lib/usertasks/descriptions/eks-authentication-mode-unsupported.md @@ -0,0 +1,3 @@ +Teleport uses the Amazon EKS API to install the Teleport Kubernetes Agent. + +Please enable API (or API and Config Map) authentication mode in the following EKS Clusters so that they can be automatically enrolled. \ No newline at end of file diff --git a/lib/usertasks/descriptions/eks-cluster-unreachable.md b/lib/usertasks/descriptions/eks-cluster-unreachable.md new file mode 100644 index 0000000000000..f1cf31beed18f --- /dev/null +++ b/lib/usertasks/descriptions/eks-cluster-unreachable.md @@ -0,0 +1,5 @@ +The EKS Cluster must be accessible from the Teleport Auth Service in order for Teleport to deploy the Teleport Kubernetes Agent. + +The following EKS Clusters couldn't be accessed. + +Ensure their network endpoint access configuration allows access from Teleport. \ No newline at end of file diff --git a/lib/usertasks/descriptions/eks-missing-endpoint-public-access.md b/lib/usertasks/descriptions/eks-missing-endpoint-public-access.md new file mode 100644 index 0000000000000..d1e2713c9b75c --- /dev/null +++ b/lib/usertasks/descriptions/eks-missing-endpoint-public-access.md @@ -0,0 +1,3 @@ +The EKS Cluster must be publicly accessible in order for Teleport to deploy the Teleport Kubernetes Agent. + +You can enable the public endpoint by accessing the Manage Endpoint Access. \ No newline at end of file diff --git a/lib/usertasks/descriptions/eks-status-not-active.md b/lib/usertasks/descriptions/eks-status-not-active.md new file mode 100644 index 0000000000000..831f22ba99f15 --- /dev/null +++ b/lib/usertasks/descriptions/eks-status-not-active.md @@ -0,0 +1,3 @@ +Only EKS Clusters whose status is active can be automatically enrolled into teleport. + +The following are not active. \ No newline at end of file diff --git a/lib/usertasks/descriptions_test.go b/lib/usertasks/descriptions_test.go index 30a358ae1ea9d..6d0d2f4cd371f 100644 --- a/lib/usertasks/descriptions_test.go +++ b/lib/usertasks/descriptions_test.go @@ -30,4 +30,7 @@ func TestAllDescriptions(t *testing.T) { for _, issueType := range usertasksapi.DiscoverEC2IssueTypes { require.NotEmpty(t, DescriptionForDiscoverEC2Issue(issueType), "issue type %q is missing descriptions/%s.md file", issueType, issueType) } + for _, issueType := range usertasksapi.DiscoverEKSIssueTypes { + require.NotEmpty(t, DescriptionForDiscoverEKSIssue(issueType), "issue type %q is missing descriptions/%s.md file", issueType, issueType) + } } diff --git a/lib/usertasks/urls.go b/lib/usertasks/urls.go new file mode 100644 index 0000000000000..1f95960af0cb2 --- /dev/null +++ b/lib/usertasks/urls.go @@ -0,0 +1,174 @@ +/* + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package usertasks + +import ( + "net/url" + "path" + + usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1" + usertasksapi "github.com/gravitational/teleport/api/types/usertasks" +) + +// UserTaskDiscoverEKSWithURLs contains the clusters that failed to auto-enroll into the cluster. +type UserTaskDiscoverEKSWithURLs struct { + *usertasksv1.DiscoverEKS + // Clusters maps a cluster name to the result of enrolling that cluster into teleport. + Clusters map[string]*DiscoverEKSClusterWithURLs `json:"clusters,omitempty"` +} + +// DiscoverEKSClusterWithURLs contains the result of enrolling an AWS EKS Cluster. +type DiscoverEKSClusterWithURLs struct { + *usertasksv1.DiscoverEKSCluster + + // ResourceURL is the Amazon Web Console URL to access this EKS Cluster. + // Always present. + // Format: https://console.aws.amazon.com/eks/home?region=#/clusters/ + ResourceURL string `json:"resourceUrl,omitempty"` + + // OpenTeleportAgentURL is the URL to open the Teleport Agent StatefulSet in Amazon EKS Web Console. + // Present when issue is of type eks-agent-not-connecting. + // Format: https://console.aws.amazon.com/eks/home?region=#/clusters//statefulsets/teleport-kube-agent?namespace=teleport-agent + OpenTeleportAgentURL string `json:"openTeleportAgentUrl,omitempty"` + + // ManageAccessURL is the URL to open the EKS in Amazon Web Console, in the Manage Access page. + // Present when issue is of type eks-authentication-mode-unsupported. + // Format: https://console.aws.amazon.com/eks/home?region=#/clusters//manage-access + ManageAccessURL string `json:"manageAccessUrl,omitempty"` + + // ManageEndpointAccessURL is the URL to open the EKS in Amazon Web Console, in the Manage Endpoint Access page. + // Present when issue is of type eks-cluster-unreachable and eks-missing-endpoint-public-access. + // Format: https://console.aws.amazon.com/eks/home?region=#/clusters//manage-endpoint-access + ManageEndpointAccessURL string `json:"manageEndpointAccessUrl,omitempty"` + + // ManageClusterURL is the URL to open the EKS Cluster in Amazon Web Console. + // Present when issue is of type eks-status-not-active. + // Format: https://console.aws.amazon.com/eks/home?region=#/clusters/ + ManageClusterURL string `json:"manageClusterUrl,omitempty"` +} + +func withEKSClusterIssueURL(metadata *usertasksv1.UserTask, cluster *usertasksv1.DiscoverEKSCluster) *DiscoverEKSClusterWithURLs { + ret := &DiscoverEKSClusterWithURLs{ + DiscoverEKSCluster: cluster, + } + clusterBaseURL := url.URL{ + Scheme: "https", + Host: "console.aws.amazon.com", + Path: path.Join("eks", "home"), + Fragment: "/clusters/" + cluster.GetName(), + RawQuery: url.Values{ + "region": []string{metadata.Spec.DiscoverEks.GetRegion()}, + }.Encode(), + } + + ret.ResourceURL = clusterBaseURL.String() + + switch metadata.Spec.IssueType { + case usertasksapi.AutoDiscoverEKSIssueAgentNotConnecting: + clusterBaseURL.Fragment = clusterBaseURL.Fragment + "/statefulsets/teleport-kube-agent?namespace=teleport-agent" + ret.OpenTeleportAgentURL = clusterBaseURL.String() + + case usertasksapi.AutoDiscoverEKSIssueAuthenticationModeUnsupported: + clusterBaseURL.Fragment = clusterBaseURL.Fragment + "/manage-access" + ret.ManageAccessURL = clusterBaseURL.String() + + case usertasksapi.AutoDiscoverEKSIssueClusterUnreachable, usertasksapi.AutoDiscoverEKSIssueMissingEndpoingPublicAccess: + clusterBaseURL.Fragment = clusterBaseURL.Fragment + "/manage-endpoint-access" + ret.ManageEndpointAccessURL = clusterBaseURL.String() + + case usertasksapi.AutoDiscoverEKSIssueStatusNotActive: + ret.ManageClusterURL = clusterBaseURL.String() + } + + return ret +} + +// EKSClustersWithURLs takes a UserTask and enriches the cluster list with URLs. +// Currently, the following URLs will be added: +// - ResourceURL: a link to open the instance in Amazon Web Console. +// The following URLs might be added depending on the issue type: +// - OpenTeleportAgentURL: links directly to the statefulset created during the helm installation +// - ManageAccessURL: links to the Manage Access screen in the Amazon EKS Web Console, for the current EKS Cluster. +// - ManageEndpointAccessURL: links to the Manage Endpoint Access screen in the Amazon EKS Web Console, for the current EKS Cluster. +// - ManageClusterURL: links to the EKS Cluster. +func EKSClustersWithURLs(ut *usertasksv1.UserTask) *UserTaskDiscoverEKSWithURLs { + clusters := ut.Spec.GetDiscoverEks().GetClusters() + clustersWithURLs := make(map[string]*DiscoverEKSClusterWithURLs, len(clusters)) + + for clusterName, cluster := range clusters { + clustersWithURLs[clusterName] = withEKSClusterIssueURL(ut, cluster) + } + + return &UserTaskDiscoverEKSWithURLs{ + DiscoverEKS: ut.Spec.GetDiscoverEks(), + Clusters: clustersWithURLs, + } +} + +// UserTaskDiscoverEC2WithURLs contains the instances that failed to auto-enroll into the cluster. +type UserTaskDiscoverEC2WithURLs struct { + *usertasksv1.DiscoverEC2 + // Instances maps the instance ID name to the result of enrolling that instance into teleport. + Instances map[string]*DiscoverEC2InstanceWithURLs `json:"clusters,omitempty"` +} + +// DiscoverEC2InstanceWithURLs contains the result of enrolling an AWS EC2 Instance. +type DiscoverEC2InstanceWithURLs struct { + *usertasksv1.DiscoverEC2Instance + + // ResourceURL is the Amazon Web Console URL to access this EC2 Instance. + // Always present. + // Format: https://console.aws.amazon.com/ec2/home?region=#InstanceDetails:instanceId= + ResourceURL string `json:"resourceUrl,omitempty"` +} + +func withEC2InstanceIssueURL(metadata *usertasksv1.UserTask, instance *usertasksv1.DiscoverEC2Instance) *DiscoverEC2InstanceWithURLs { + ret := &DiscoverEC2InstanceWithURLs{ + DiscoverEC2Instance: instance, + } + instanceBaseURL := url.URL{ + Scheme: "https", + Host: "console.aws.amazon.com", + Path: path.Join("ec2", "home"), + Fragment: "InstanceDetails:instanceId=" + instance.GetInstanceId(), + RawQuery: url.Values{ + "region": []string{metadata.Spec.DiscoverEc2.GetRegion()}, + }.Encode(), + } + ret.ResourceURL = instanceBaseURL.String() + + return ret +} + +// EC2InstancesWithURLs takes a UserTask and enriches the instance list with URLs. +// Currently, the following URLs will be added: +// - ResourceURL: a link to open the instance in Amazon Web Console. +func EC2InstancesWithURLs(ut *usertasksv1.UserTask) *UserTaskDiscoverEC2WithURLs { + instances := ut.Spec.GetDiscoverEc2().GetInstances() + instancesWithURLs := make(map[string]*DiscoverEC2InstanceWithURLs, len(instances)) + + for instanceID, instance := range instances { + instancesWithURLs[instanceID] = withEC2InstanceIssueURL(ut, instance) + } + + return &UserTaskDiscoverEC2WithURLs{ + DiscoverEC2: ut.Spec.GetDiscoverEc2(), + Instances: instancesWithURLs, + } +} diff --git a/lib/usertasks/urls_test.go b/lib/usertasks/urls_test.go new file mode 100644 index 0000000000000..74f8e6c065fb3 --- /dev/null +++ b/lib/usertasks/urls_test.go @@ -0,0 +1,151 @@ +/* + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package usertasks + +import ( + "testing" + + "github.com/stretchr/testify/require" + + usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1" + usertasksapi "github.com/gravitational/teleport/api/types/usertasks" +) + +func TestEKSURLs(t *testing.T) { + clusterName := "my-cluster" + dummyCluster := &usertasksv1.DiscoverEKSCluster{Name: clusterName} + baseClusterData := &usertasksv1.DiscoverEKS{ + Region: "us-east-1", + Clusters: map[string]*usertasksv1.DiscoverEKSCluster{ + clusterName: dummyCluster, + }, + } + + for _, tt := range []struct { + name string + issueType string + expectedEKSClusterWithURL *DiscoverEKSClusterWithURLs + expected *UserTaskDiscoverEKSWithURLs + }{ + { + name: "url for eks agent not connecting", + issueType: usertasksapi.AutoDiscoverEKSIssueAgentNotConnecting, + expectedEKSClusterWithURL: &DiscoverEKSClusterWithURLs{ + ResourceURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster", + OpenTeleportAgentURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster/statefulsets/teleport-kube-agent?namespace=teleport-agent", + }, + }, + { + name: "url for eks authentication mode unsupported", + issueType: usertasksapi.AutoDiscoverEKSIssueAuthenticationModeUnsupported, + expectedEKSClusterWithURL: &DiscoverEKSClusterWithURLs{ + ResourceURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster", + ManageAccessURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster/manage-access", + }, + }, + { + name: "url for eks cluster unreachable", + issueType: usertasksapi.AutoDiscoverEKSIssueClusterUnreachable, + expectedEKSClusterWithURL: &DiscoverEKSClusterWithURLs{ + ResourceURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster", + ManageEndpointAccessURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster/manage-endpoint-access", + }, + }, + { + name: "url for eks missing endpoint public access", + issueType: usertasksapi.AutoDiscoverEKSIssueMissingEndpoingPublicAccess, + expectedEKSClusterWithURL: &DiscoverEKSClusterWithURLs{ + ResourceURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster", + ManageEndpointAccessURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster/manage-endpoint-access", + }, + }, + { + name: "url for eks cluster status not active", + issueType: usertasksapi.AutoDiscoverEKSIssueStatusNotActive, + expectedEKSClusterWithURL: &DiscoverEKSClusterWithURLs{ + ResourceURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster", + ManageClusterURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster", + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + clusterWithURL := tt.expectedEKSClusterWithURL + clusterWithURL.DiscoverEKSCluster = dummyCluster + expected := &UserTaskDiscoverEKSWithURLs{ + DiscoverEKS: baseClusterData, + Clusters: map[string]*DiscoverEKSClusterWithURLs{ + clusterName: clusterWithURL, + }, + } + + got := EKSClustersWithURLs(&usertasksv1.UserTask{ + Spec: &usertasksv1.UserTaskSpec{ + IssueType: tt.issueType, + DiscoverEks: baseClusterData, + }, + }) + require.Equal(t, expected, got) + }) + } +} + +func TestEC2URLs(t *testing.T) { + instanceID := "i-12345678" + dummyInstance := &usertasksv1.DiscoverEC2Instance{InstanceId: instanceID} + baseInstancesData := &usertasksv1.DiscoverEC2{ + Region: "us-east-1", + Instances: map[string]*usertasksv1.DiscoverEC2Instance{ + instanceID: dummyInstance, + }, + } + + for _, tt := range []struct { + name string + issueType string + expectedEC2InstanceWithURL *DiscoverEC2InstanceWithURLs + expected *UserTaskDiscoverEC2WithURLs + }{ + { + name: "url for ec2 resource", + issueType: usertasksapi.AutoDiscoverEC2IssueSSMScriptFailure, + expectedEC2InstanceWithURL: &DiscoverEC2InstanceWithURLs{ + ResourceURL: "https://console.aws.amazon.com/ec2/home?region=us-east-1#InstanceDetails:instanceId=i-12345678", + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + instanceWithURL := tt.expectedEC2InstanceWithURL + instanceWithURL.DiscoverEC2Instance = dummyInstance + expected := &UserTaskDiscoverEC2WithURLs{ + DiscoverEC2: baseInstancesData, + Instances: map[string]*DiscoverEC2InstanceWithURLs{ + instanceID: instanceWithURL, + }, + } + + got := EC2InstancesWithURLs(&usertasksv1.UserTask{ + Spec: &usertasksv1.UserTaskSpec{ + IssueType: tt.issueType, + DiscoverEc2: baseInstancesData, + }, + }) + require.Equal(t, expected, got) + }) + } +} diff --git a/lib/utils/log/formatter_test.go b/lib/utils/log/formatter_test.go index e11a9f63620fb..9abb0310ba0be 100644 --- a/lib/utils/log/formatter_test.go +++ b/lib/utils/log/formatter_test.go @@ -51,7 +51,7 @@ var ( logErr = errors.New("the quick brown fox jumped really high") addr = fakeAddr{addr: "127.0.0.1:1234"} - fields = logrus.Fields{ + fields = map[string]any{ "local": &addr, "remote": &addr, "login": "llama", diff --git a/lib/utils/log/handle_state.go b/lib/utils/log/handle_state.go index c8ac9913781ca..3f88e100933ac 100644 --- a/lib/utils/log/handle_state.go +++ b/lib/utils/log/handle_state.go @@ -14,7 +14,6 @@ import ( "time" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" ) @@ -114,13 +113,6 @@ func (s *handleState) appendAttr(a slog.Attr) bool { } } return nonEmpty - case logrus.Fields: - for k, v := range fields { - if s.appendAttr(slog.Any(k, v)) { - nonEmpty = true - } - } - return nonEmpty } } diff --git a/lib/utils/log/levels.go b/lib/utils/log/levels.go deleted file mode 100644 index 747561ffb155b..0000000000000 --- a/lib/utils/log/levels.go +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Teleport - * Copyright (C) 2023 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package log - -import ( - "log/slog" - - "github.com/sirupsen/logrus" -) - -// SupportedLevelsText lists the supported log levels in their text -// representation. All strings are in uppercase. -var SupportedLevelsText = []string{ - TraceLevelText, - slog.LevelDebug.String(), - slog.LevelInfo.String(), - slog.LevelWarn.String(), - slog.LevelError.String(), -} - -// SlogLevelToLogrusLevel converts a [slog.Level] to its equivalent -// [logrus.Level]. -func SlogLevelToLogrusLevel(level slog.Level) logrus.Level { - switch level { - case TraceLevel: - return logrus.TraceLevel - case slog.LevelDebug: - return logrus.DebugLevel - case slog.LevelInfo: - return logrus.InfoLevel - case slog.LevelWarn: - return logrus.WarnLevel - case slog.LevelError: - return logrus.ErrorLevel - default: - return logrus.FatalLevel - } -} diff --git a/lib/utils/log/log.go b/lib/utils/log/log.go new file mode 100644 index 0000000000000..2f16b902e3df6 --- /dev/null +++ b/lib/utils/log/log.go @@ -0,0 +1,128 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package log + +import ( + "context" + "io" + "io/fs" + "log/slog" + "os" + "strings" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport" +) + +// Config configures teleport logging +type Config struct { + // Output defines where logs go. It can be one of the following: "stderr", "stdout" or + // a path to a log file + Output string + // Severity defines how verbose the log will be. Possible values are "error", "info", "warn" + Severity string + // Format defines the output format. Possible values are 'text' and 'json'. + Format string + // ExtraFields lists the output fields from KnownFormatFields. Example format: [timestamp, component, caller] + ExtraFields []string + // EnableColors dictates if output should be colored. + EnableColors bool +} + +// Initialize configures the default global logger based on the +// provided configuration. The [slog.Logger] and [slog.LevelVar] +func Initialize(loggerConfig Config) (*slog.Logger, *slog.LevelVar, error) { + const ( + // logFileDefaultMode is the preferred permissions mode for log file. + logFileDefaultMode fs.FileMode = 0o644 + // logFileDefaultFlag is the preferred flags set to log file. + logFileDefaultFlag = os.O_WRONLY | os.O_CREATE | os.O_APPEND + ) + + var w io.Writer + level := new(slog.LevelVar) + switch loggerConfig.Output { + case "": + w = os.Stderr + case "stderr", "error", "2": + w = os.Stderr + case "stdout", "out", "1": + w = os.Stdout + case teleport.Syslog: + var err error + w, err = NewSyslogWriter() + if err != nil { + slog.ErrorContext(context.Background(), "Failed to switch logging to syslog", "error", err) + slog.SetDefault(slog.New(DiscardHandler{})) + return slog.Default(), level, nil + } + default: + // Assume a file path for all other provided output values. + sharedWriter, err := NewFileSharedWriter(loggerConfig.Output, logFileDefaultFlag, logFileDefaultMode) + if err != nil { + return nil, nil, trace.Wrap(err, "failed to init the log file shared writer") + } + w = NewWriterFinalizer(sharedWriter) + if err := sharedWriter.RunWatcherReopen(context.Background()); err != nil { + return nil, nil, trace.Wrap(err) + } + } + + switch strings.ToLower(loggerConfig.Severity) { + case "", "info": + level.Set(slog.LevelInfo) + case "err", "error": + level.Set(slog.LevelError) + case teleport.DebugLevel: + level.Set(slog.LevelDebug) + case "warn", "warning": + level.Set(slog.LevelWarn) + case "trace": + level.Set(TraceLevel) + default: + return nil, nil, trace.BadParameter("unsupported logger severity: %q", loggerConfig.Severity) + } + + configuredFields, err := ValidateFields(loggerConfig.ExtraFields) + if err != nil { + return nil, nil, trace.Wrap(err) + } + + var logger *slog.Logger + switch strings.ToLower(loggerConfig.Format) { + case "": + fallthrough // not set. defaults to 'text' + case "text": + logger = slog.New(NewSlogTextHandler(w, SlogTextHandlerConfig{ + Level: level, + EnableColors: loggerConfig.EnableColors, + ConfiguredFields: configuredFields, + })) + slog.SetDefault(logger) + case "json": + logger = slog.New(NewSlogJSONHandler(w, SlogJSONHandlerConfig{ + Level: level, + ConfiguredFields: configuredFields, + })) + slog.SetDefault(logger) + default: + return nil, nil, trace.BadParameter("unsupported log output format : %q", loggerConfig.Format) + } + + return logger, level, nil +} diff --git a/lib/utils/log/logrus_formatter.go b/lib/utils/log/logrus_formatter.go index a21d922adf809..14ad8441da7cc 100644 --- a/lib/utils/log/logrus_formatter.go +++ b/lib/utils/log/logrus_formatter.go @@ -25,7 +25,6 @@ import ( "slices" "strconv" "strings" - "unicode" "github.com/gravitational/trace" "github.com/sirupsen/logrus" @@ -76,27 +75,6 @@ func (w *writer) Bytes() []byte { return *w.b } -const ( - noColor = -1 - red = 31 - yellow = 33 - blue = 36 - gray = 37 - // LevelField is the log field that stores the verbosity. - LevelField = "level" - // ComponentField is the log field that stores the calling component. - ComponentField = "component" - // CallerField is the log field that stores the calling file and line number. - CallerField = "caller" - // TimestampField is the field that stores the timestamp the log was emitted. - TimestampField = "timestamp" - messageField = "message" - // defaultComponentPadding is a default padding for component field - defaultComponentPadding = 11 - // defaultLevelPadding is a default padding for level field - defaultLevelPadding = 4 -) - // NewDefaultTextFormatter creates a TextFormatter with // the default options set. func NewDefaultTextFormatter(enableColors bool) *TextFormatter { @@ -304,15 +282,6 @@ func (w *writer) writeError(value interface{}) { } } -func padMax(in string, chars int) string { - switch { - case len(in) < chars: - return in + strings.Repeat(" ", chars-len(in)) - default: - return in[:chars] - } -} - func (w *writer) writeField(value interface{}, color int) { if w.Len() > 0 { w.WriteByte(' ') @@ -456,33 +425,3 @@ func frameToTrace(frame runtime.Frame) trace.Trace { Line: frame.Line, } } - -var defaultFormatFields = []string{LevelField, ComponentField, CallerField, TimestampField} - -var knownFormatFields = map[string]struct{}{ - LevelField: {}, - ComponentField: {}, - CallerField: {}, - TimestampField: {}, -} - -func ValidateFields(formatInput []string) (result []string, err error) { - for _, component := range formatInput { - component = strings.TrimSpace(component) - if _, ok := knownFormatFields[component]; !ok { - return nil, trace.BadParameter("invalid log format key: %q", component) - } - result = append(result, component) - } - return result, nil -} - -// needsQuoting returns true if any non-printable characters are found. -func needsQuoting(text string) bool { - for _, r := range text { - if !unicode.IsPrint(r) { - return true - } - } - return false -} diff --git a/lib/utils/log/slog.go b/lib/utils/log/slog.go index b1b0678ec5487..46f0e13627b3e 100644 --- a/lib/utils/log/slog.go +++ b/lib/utils/log/slog.go @@ -24,7 +24,10 @@ import ( "log/slog" "reflect" "strings" + "unicode" + "github.com/gravitational/trace" + "github.com/sirupsen/logrus" oteltrace "go.opentelemetry.io/otel/trace" ) @@ -34,8 +37,56 @@ const ( // TraceLevelText is the text representation of Trace verbosity. TraceLevelText = "TRACE" + + noColor = -1 + red = 31 + yellow = 33 + blue = 36 + gray = 37 + // LevelField is the log field that stores the verbosity. + LevelField = "level" + // ComponentField is the log field that stores the calling component. + ComponentField = "component" + // CallerField is the log field that stores the calling file and line number. + CallerField = "caller" + // TimestampField is the field that stores the timestamp the log was emitted. + TimestampField = "timestamp" + messageField = "message" + // defaultComponentPadding is a default padding for component field + defaultComponentPadding = 11 + // defaultLevelPadding is a default padding for level field + defaultLevelPadding = 4 ) +// SupportedLevelsText lists the supported log levels in their text +// representation. All strings are in uppercase. +var SupportedLevelsText = []string{ + TraceLevelText, + slog.LevelDebug.String(), + slog.LevelInfo.String(), + slog.LevelWarn.String(), + slog.LevelError.String(), +} + +// SlogLevelToLogrusLevel converts a [slog.Level] to its equivalent +// [logrus.Level]. +func SlogLevelToLogrusLevel(level slog.Level) logrus.Level { + switch level { + case TraceLevel: + return logrus.TraceLevel + case slog.LevelDebug: + return logrus.DebugLevel + case slog.LevelInfo: + return logrus.InfoLevel + case slog.LevelWarn: + return logrus.WarnLevel + case slog.LevelError: + return logrus.ErrorLevel + default: + return logrus.FatalLevel + } +} + // DiscardHandler is a [slog.Handler] that discards all messages. It // is more efficient than a [slog.Handler] which outputs to [io.Discard] since // it performs zero formatting. @@ -68,6 +119,47 @@ func addTracingContextToRecord(ctx context.Context, r *slog.Record) { } } +var defaultFormatFields = []string{LevelField, ComponentField, CallerField, TimestampField} + +var knownFormatFields = map[string]struct{}{ + LevelField: {}, + ComponentField: {}, + CallerField: {}, + TimestampField: {}, +} + +// ValidateFields ensures the provided fields map to the allowed fields. An error +// is returned if any of the fields are invalid. +func ValidateFields(formatInput []string) (result []string, err error) { + for _, component := range formatInput { + component = strings.TrimSpace(component) + if _, ok := knownFormatFields[component]; !ok { + return nil, trace.BadParameter("invalid log format key: %q", component) + } + result = append(result, component) + } + return result, nil +} + +// needsQuoting returns true if any non-printable characters are found. +func needsQuoting(text string) bool { + for _, r := range text { + if !unicode.IsPrint(r) { + return true + } + } + return false +} + +func padMax(in string, chars int) string { + switch { + case len(in) < chars: + return in + strings.Repeat(" ", chars-len(in)) + default: + return in[:chars] + } +} + // getCaller retrieves source information from the attribute // and returns the file and line of the caller. The file is // truncated from the absolute path to package/filename. diff --git a/lib/utils/log/slog_text_handler.go b/lib/utils/log/slog_text_handler.go index b3bc4900ac64c..7f93a388977bb 100644 --- a/lib/utils/log/slog_text_handler.go +++ b/lib/utils/log/slog_text_handler.go @@ -27,7 +27,6 @@ import ( "sync" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport" ) @@ -324,12 +323,6 @@ func (s *SlogTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { nonEmpty = true } } - case logrus.Fields: - for k, v := range fields { - if state.appendAttr(slog.Any(k, v)) { - nonEmpty = true - } - } } default: if state.appendAttr(a) { diff --git a/web/packages/shared/components/ToolTip/index.ts b/lib/utils/log/syslog.go similarity index 68% rename from web/packages/shared/components/ToolTip/index.ts rename to lib/utils/log/syslog.go index f1be185cb4ae6..40896ebedc767 100644 --- a/web/packages/shared/components/ToolTip/index.ts +++ b/lib/utils/log/syslog.go @@ -1,4 +1,7 @@ -/** +//go:build !windows +// +build !windows + +/* * Teleport * Copyright (C) 2023 Gravitational, Inc. * @@ -16,10 +19,17 @@ * along with this program. If not, see . */ -export { - /** @deprecated Use `TooltipInfo` from `design/Tooltip` */ - IconTooltip as ToolTipInfo, +package log + +import ( + "io" + "log/syslog" + + "github.com/gravitational/trace" +) - /** @deprecated Use `HoverTooltip` from `design/Tooltip` */ - HoverTooltip, -} from 'design/Tooltip'; +// NewSyslogWriter creates a writer that outputs to the local machine syslog. +func NewSyslogWriter() (io.Writer, error) { + writer, err := syslog.Dial("", "", syslog.LOG_WARNING, "") + return writer, trace.Wrap(err) +} diff --git a/lib/utils/syslog_windows.go b/lib/utils/log/syslog_windows.go similarity index 81% rename from lib/utils/syslog_windows.go rename to lib/utils/log/syslog_windows.go index 7812dddabb237..3d359bdc1d437 100644 --- a/lib/utils/syslog_windows.go +++ b/lib/utils/log/syslog_windows.go @@ -16,20 +16,14 @@ * along with this program. If not, see . */ -package utils +package log import ( "io" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" ) -// NewSyslogHook always returns an error on Windows. -func NewSyslogHook(io.Writer) (logrus.Hook, error) { - return nil, trace.NotImplemented("cannot use syslog on Windows") -} - // NewSyslogWriter always returns an error on Windows. func NewSyslogWriter() (io.Writer, error) { return nil, trace.NotImplemented("cannot use syslog on Windows") diff --git a/lib/utils/syslog.go b/lib/utils/syslog.go deleted file mode 100644 index 86123bda5e1c0..0000000000000 --- a/lib/utils/syslog.go +++ /dev/null @@ -1,77 +0,0 @@ -//go:build !windows -// +build !windows - -/* - * Teleport - * Copyright (C) 2023 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package utils - -import ( - "io" - "log/syslog" - "os" - - "github.com/gravitational/trace" - "github.com/sirupsen/logrus" - logrusSyslog "github.com/sirupsen/logrus/hooks/syslog" -) - -// SwitchLoggingToSyslog configures the default logger to send output to syslog. -func SwitchLoggingToSyslog() error { - logger := logrus.StandardLogger() - - w, err := NewSyslogWriter() - if err != nil { - logger.Errorf("Failed to switch logging to syslog: %v.", err) - logger.SetOutput(os.Stderr) - return trace.Wrap(err) - } - - hook, err := NewSyslogHook(w) - if err != nil { - logger.Errorf("Failed to switch logging to syslog: %v.", err) - logger.SetOutput(os.Stderr) - return trace.Wrap(err) - } - - logger.ReplaceHooks(make(logrus.LevelHooks)) - logger.AddHook(hook) - logger.SetOutput(io.Discard) - - return nil -} - -// NewSyslogHook provides a [logrus.Hook] that sends output to syslog. -func NewSyslogHook(w io.Writer) (logrus.Hook, error) { - if w == nil { - return nil, trace.BadParameter("syslog writer must not be nil") - } - - sw, ok := w.(*syslog.Writer) - if !ok { - return nil, trace.BadParameter("expected a syslog writer, got %T", w) - } - - return &logrusSyslog.SyslogHook{Writer: sw}, nil -} - -// NewSyslogWriter creates a writer that outputs to the local machine syslog. -func NewSyslogWriter() (io.Writer, error) { - writer, err := syslog.Dial("", "", syslog.LOG_WARNING, "") - return writer, trace.Wrap(err) -} diff --git a/lib/web/ui/usertask.go b/lib/web/ui/usertask.go index 02b174aeb1782..14fc86a8bd71f 100644 --- a/lib/web/ui/usertask.go +++ b/lib/web/ui/usertask.go @@ -24,6 +24,7 @@ import ( "github.com/gravitational/trace" usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1" + apiusertasks "github.com/gravitational/teleport/api/types/usertasks" "github.com/gravitational/teleport/lib/usertasks" ) @@ -51,7 +52,9 @@ type UserTaskDetail struct { // Description is a markdown document that explains the issue and how to fix it. Description string `json:"description,omitempty"` // DiscoverEC2 contains the task details for the DiscoverEC2 tasks. - DiscoverEC2 *usertasksv1.DiscoverEC2 `json:"discoverEc2,omitempty"` + DiscoverEC2 *usertasks.UserTaskDiscoverEC2WithURLs `json:"discoverEc2,omitempty"` + // DiscoverEKS contains the task details for the DiscoverEKS tasks. + DiscoverEKS *usertasks.UserTaskDiscoverEKSWithURLs `json:"discoverEks,omitempty"` } // UpdateUserTaskStateRequest is a request to update a UserTask @@ -92,10 +95,24 @@ func MakeUserTasks(uts []*usertasksv1.UserTask) []UserTask { // MakeDetailedUserTask creates a UI UserTask representation containing all the details. func MakeDetailedUserTask(ut *usertasksv1.UserTask) UserTaskDetail { + var description string + var discoverEKS *usertasks.UserTaskDiscoverEKSWithURLs + var discoverEC2 *usertasks.UserTaskDiscoverEC2WithURLs + + switch ut.GetSpec().GetTaskType() { + case apiusertasks.TaskTypeDiscoverEC2: + description = usertasks.DescriptionForDiscoverEC2Issue(ut.GetSpec().GetIssueType()) + discoverEC2 = usertasks.EC2InstancesWithURLs(ut) + case apiusertasks.TaskTypeDiscoverEKS: + description = usertasks.DescriptionForDiscoverEKSIssue(ut.GetSpec().GetIssueType()) + discoverEKS = usertasks.EKSClustersWithURLs(ut) + } + return UserTaskDetail{ UserTask: MakeUserTask(ut), - Description: usertasks.DescriptionForDiscoverEC2Issue(ut.GetSpec().GetIssueType()), - DiscoverEC2: ut.GetSpec().GetDiscoverEc2(), + Description: description, + DiscoverEC2: discoverEC2, + DiscoverEKS: discoverEKS, } } diff --git a/package.json b/package.json index e8b5ab7246759..a5485c04dc1b4 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "type-check": "NODE_OPTIONS='--max-old-space-size=4096' tsc --build", "prettier-check": "prettier --check '+(e|web)/**/*.{ts,tsx,js,jsx,mts}'", "prettier-write": "prettier --write --log-level silent '+(e|web)/**/*.{ts,tsx,js,jsx,mts}'", - "process-icons": "node web/packages/design/src/Icon/script/script.js & pnpm prettier --loglevel silent --write 'web/packages/design/src/Icon/Icons/*.tsx'", + "process-icons": "node web/packages/design/src/Icon/script/script.js & pnpm prettier --log-level silent --write 'web/packages/design/src/Icon/**/*.tsx'", "nop": "exit 0" }, "private": true, diff --git a/tool/tbot/spiffe.go b/tool/tbot/spiffe.go index f319505de8caa..9ba58f3c94df0 100644 --- a/tool/tbot/spiffe.go +++ b/tool/tbot/spiffe.go @@ -21,22 +21,48 @@ package main import ( "context" "fmt" + "log/slog" "time" "github.com/gravitational/trace" "github.com/spiffe/go-spiffe/v2/svid/jwtsvid" "github.com/spiffe/go-spiffe/v2/workloadapi" - - "github.com/gravitational/teleport/lib/utils" ) +// TODO(tross/noah): Remove once go-spiff has a slog<->workloadapi.Logger adapter. +// https://github.com/spiffe/go-spiffe/issues/281 +type logger struct { + l *slog.Logger +} + +func (l logger) Debugf(format string, args ...interface{}) { + //nolint:sloglint // msg cannot be constant + l.l.DebugContext(context.Background(), fmt.Sprintf(format, args...)) +} + +func (l logger) Infof(format string, args ...interface{}) { + //nolint:sloglint // msg cannot be constant + l.l.InfoContext(context.Background(), fmt.Sprintf(format, args...)) +} + +func (l logger) Warnf(format string, args ...interface{}) { + //nolint:sloglint // msg cannot be constant + l.l.WarnContext(context.Background(), fmt.Sprintf(format, args...)) +} + +func (l logger) Errorf(format string, args ...interface{}) { + //nolint:sloglint // msg cannot be constant + l.l.ErrorContext(context.Background(), fmt.Sprintf(format, args...)) +} + func onSPIFFEInspect(ctx context.Context, path string) error { log.InfoContext(ctx, "Inspecting SPIFFE Workload API Endpoint", "path", path) source, err := workloadapi.New( ctx, // TODO(noah): Upstream PR to add slog<->workloadapi.Logger adapter. - workloadapi.WithLogger(utils.NewLogger()), + // https://github.com/spiffe/go-spiffe/issues/281 + workloadapi.WithLogger(logger{l: slog.Default()}), workloadapi.WithAddr(path), ) if err != nil { diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index c31d2a25ed0bf..c1ea21addc2b6 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -1908,7 +1908,7 @@ type autoUpdateConfigCollection struct { } func (c *autoUpdateConfigCollection) resources() []types.Resource { - return []types.Resource{types.Resource153ToLegacy(c.config)} + return []types.Resource{types.ProtoResource153ToLegacy(c.config)} } func (c *autoUpdateConfigCollection) writeText(w io.Writer, verbose bool) error { @@ -1926,7 +1926,7 @@ type autoUpdateVersionCollection struct { } func (c *autoUpdateVersionCollection) resources() []types.Resource { - return []types.Resource{types.Resource153ToLegacy(c.version)} + return []types.Resource{types.ProtoResource153ToLegacy(c.version)} } func (c *autoUpdateVersionCollection) writeText(w io.Writer, verbose bool) error { @@ -1944,7 +1944,7 @@ type autoUpdateAgentRolloutCollection struct { } func (c *autoUpdateAgentRolloutCollection) resources() []types.Resource { - return []types.Resource{types.Resource153ToLegacy(c.rollout)} + return []types.Resource{types.ProtoResource153ToLegacy(c.rollout)} } func (c *autoUpdateAgentRolloutCollection) writeText(w io.Writer, verbose bool) error { diff --git a/tool/tctl/common/collection_test.go b/tool/tctl/common/collection_test.go index 166c5f6901599..f0679b9a65581 100644 --- a/tool/tctl/common/collection_test.go +++ b/tool/tctl/common/collection_test.go @@ -27,13 +27,19 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/durationpb" + kyaml "k8s.io/apimachinery/pkg/util/yaml" "github.com/gravitational/teleport/api" + autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" dbobjectimportrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/autoupdate" "github.com/gravitational/teleport/api/types/label" "github.com/gravitational/teleport/lib/asciitable" + "github.com/gravitational/teleport/lib/defaults" + "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/srv/db/common/databaseobject" "github.com/gravitational/teleport/lib/srv/db/common/databaseobjectimportrule" "github.com/gravitational/teleport/tool/common" @@ -431,3 +437,66 @@ func makeTestLabels(extraStaticLabels map[string]string) map[string]string { maps.Copy(labels, extraStaticLabels) return labels } + +// autoUpdateConfigBrokenCollection is an intentionally broken version of the +// autoUpdateConfigCollection that is not marshaling resources properly because +// it's doing json marshaling instead of protojson marshaling. +type autoUpdateConfigBrokenCollection struct { + autoUpdateConfigCollection +} + +func (c *autoUpdateConfigBrokenCollection) resources() []types.Resource { + // We use Resource153ToLegacy instead of ProtoResource153ToLegacy. + return []types.Resource{types.Resource153ToLegacy(c.config)} +} + +// This test makes sure we marshal and unmarshal proto-based Resource153 properly. +// We had a bug where types.Resource153 implemented by protobuf structs were not +// marshaled properly (they should be marshaled using protojson). This test +// checks we can do a round-trip with one of those proto-struct resource. +func TestRoundTripProtoResource153(t *testing.T) { + // Test setup: generate fixture. + initial, err := autoupdate.NewAutoUpdateConfig(&autoupdatev1pb.AutoUpdateConfigSpec{ + Agents: &autoupdatev1pb.AutoUpdateConfigSpecAgents{ + Mode: autoupdate.AgentsUpdateModeEnabled, + Strategy: autoupdate.AgentsStrategyTimeBased, + MaintenanceWindowDuration: durationpb.New(1 * time.Hour), + Schedules: &autoupdatev1pb.AgentAutoUpdateSchedules{ + Regular: []*autoupdatev1pb.AgentAutoUpdateGroup{ + { + Name: "group1", + Days: []string{types.Wildcard}, + }, + }, + }, + }, + }) + require.NoError(t, err) + + // Test execution: dump the resource into a YAML manifest. + collection := &autoUpdateConfigCollection{config: initial} + buf := &bytes.Buffer{} + require.NoError(t, writeYAML(collection, buf)) + + // Test execution: load the YAML manifest back. + decoder := kyaml.NewYAMLOrJSONDecoder(buf, defaults.LookaheadBufSize) + var raw services.UnknownResource + require.NoError(t, decoder.Decode(&raw)) + result, err := services.UnmarshalProtoResource[*autoupdatev1pb.AutoUpdateConfig](raw.Raw) + require.NoError(t, err) + + // Test validation: check that the loaded content matches what we had before. + require.Equal(t, result, initial) + + // Test execution: now dump the resource into a YAML manifest with a + // collection using types.Resource153ToLegacy instead of types.ProtoResource153ToLegacy + brokenCollection := &autoUpdateConfigBrokenCollection{autoUpdateConfigCollection{initial}} + buf = &bytes.Buffer{} + require.NoError(t, writeYAML(brokenCollection, buf)) + + // Test execution: load the YAML manifest back and see that we can't unmarshal it. + decoder = kyaml.NewYAMLOrJSONDecoder(buf, defaults.LookaheadBufSize) + require.NoError(t, decoder.Decode(&raw)) + _, err = services.UnmarshalProtoResource[*autoupdatev1pb.AutoUpdateConfig](raw.Raw) + require.Error(t, err) +} diff --git a/tool/tctl/common/helpers_test.go b/tool/tctl/common/helpers_test.go index 0cf773852c96f..b235a40e8b5e2 100644 --- a/tool/tctl/common/helpers_test.go +++ b/tool/tctl/common/helpers_test.go @@ -35,6 +35,7 @@ import ( "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" "gopkg.in/yaml.v2" + kyaml "k8s.io/apimachinery/pkg/util/yaml" "github.com/gravitational/teleport/api/breaker" apidefaults "github.com/gravitational/teleport/api/defaults" @@ -43,6 +44,7 @@ import ( "github.com/gravitational/teleport/lib/config" "github.com/gravitational/teleport/lib/service" "github.com/gravitational/teleport/lib/service/servicecfg" + "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/utils" commonclient "github.com/gravitational/teleport/tool/tctl/common/client" tctlcfg "github.com/gravitational/teleport/tool/tctl/common/config" @@ -153,6 +155,13 @@ func mustDecodeJSON[T any](t *testing.T, r io.Reader) T { return out } +func mustTranscodeYAMLToJSON(t *testing.T, r io.Reader) []byte { + decoder := kyaml.NewYAMLToJSONDecoder(r) + var resource services.UnknownResource + require.NoError(t, decoder.Decode(&resource)) + return resource.Raw +} + func mustDecodeYAMLDocuments[T any](t *testing.T, r io.Reader, out *[]T) { t.Helper() decoder := yaml.NewDecoder(r) diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 23749bc14c528..7ea28fc994402 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -507,7 +507,7 @@ func (rc *ResourceCommand) createRole(ctx context.Context, client *authclient.Cl return trace.Wrap(err) } - warnAboutKubernetesResources(rc.config.Log, role) + warnAboutKubernetesResources(ctx, rc.config.Logger, role) roleName := role.GetName() _, err = client.GetRole(ctx, roleName) @@ -536,8 +536,8 @@ func (rc *ResourceCommand) updateRole(ctx context.Context, client *authclient.Cl return trace.Wrap(err) } - warnAboutKubernetesResources(rc.config.Log, role) - warnAboutDynamicLabelsInDenyRule(rc.config.Log, role) + warnAboutKubernetesResources(ctx, rc.config.Logger, role) + warnAboutDynamicLabelsInDenyRule(ctx, rc.config.Logger, role) if _, err := client.UpdateRole(ctx, role); err != nil { return trace.Wrap(err) @@ -548,21 +548,21 @@ func (rc *ResourceCommand) updateRole(ctx context.Context, client *authclient.Cl // warnAboutKubernetesResources warns about kubernetes resources // if kubernetes_labels are set but kubernetes_resources are not. -func warnAboutKubernetesResources(logger utils.Logger, r types.Role) { +func warnAboutKubernetesResources(ctx context.Context, logger *slog.Logger, r types.Role) { role, ok := r.(*types.RoleV6) // only warn about kubernetes resources for v6 roles if !ok || role.Version != types.V6 { return } if len(role.Spec.Allow.KubernetesLabels) > 0 && len(role.Spec.Allow.KubernetesResources) == 0 { - logger.Warningf("role %q has allow.kubernetes_labels set but no allow.kubernetes_resources, this is probably a mistake. Teleport will restrict access to pods.", role.Metadata.Name) + logger.WarnContext(ctx, "role has allow.kubernetes_labels set but no allow.kubernetes_resources, this is probably a mistake - Teleport will restrict access to pods", "role", role.Metadata.Name) } if len(role.Spec.Allow.KubernetesLabels) == 0 && len(role.Spec.Allow.KubernetesResources) > 0 { - logger.Warningf("role %q has allow.kubernetes_resources set but no allow.kubernetes_labels, this is probably a mistake. kubernetes_resources won't be effective.", role.Metadata.Name) + logger.WarnContext(ctx, "role has allow.kubernetes_resources set but no allow.kubernetes_labels, this is probably a mistake - kubernetes_resources won't be effective", "role", role.Metadata.Name) } if len(role.Spec.Deny.KubernetesLabels) > 0 && len(role.Spec.Deny.KubernetesResources) > 0 { - logger.Warningf("role %q has deny.kubernetes_labels set but also has deny.kubernetes_resources set, this is probably a mistake. deny.kubernetes_resources won't be effective.", role.Metadata.Name) + logger.WarnContext(ctx, "role has deny.kubernetes_labels set but also has deny.kubernetes_resources set, this is probably a mistake - deny.kubernetes_resources won't be effective", "role", role.Metadata.Name) } } @@ -574,13 +574,13 @@ func dynamicLabelWarningMessage(r types.Role) string { // warnAboutDynamicLabelsInDenyRule warns about using dynamic/ labels in deny // rules. Only applies to existing roles as adding dynamic/ labels to deny // rules in a new role is not allowed. -func warnAboutDynamicLabelsInDenyRule(logger utils.Logger, r types.Role) { +func warnAboutDynamicLabelsInDenyRule(ctx context.Context, logger *slog.Logger, r types.Role) { if err := services.CheckDynamicLabelsInDenyRules(r); err == nil { return } else if trace.IsBadParameter(err) { - logger.Warningf(dynamicLabelWarningMessage(r)) + logger.WarnContext(ctx, "existing role has labels with the a dynamic prefix in its deny rules, this is not recommended due to the volatility of dynamic labels and is not allowed for new roles", "role", r.GetName()) } else { - logger.WithError(err).Warningf("error checking deny rules labels") + logger.WarnContext(ctx, "error checking deny rules labels", "error", err) } } @@ -2357,7 +2357,7 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *authclient if err != nil { return nil, trace.Wrap(err) } - warnAboutDynamicLabelsInDenyRule(rc.config.Log, role) + warnAboutDynamicLabelsInDenyRule(ctx, rc.config.Logger, role) return &roleCollection{roles: []types.Role{role}}, nil case types.KindNamespace: if rc.ref.Name == "" { diff --git a/tool/tctl/common/resource_command_test.go b/tool/tctl/common/resource_command_test.go index 61b2c2650f53a..ff280c07a6f08 100644 --- a/tool/tctl/common/resource_command_test.go +++ b/tool/tctl/common/resource_command_test.go @@ -36,6 +36,7 @@ import ( "github.com/jonboulle/clockwork" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/testing/protocmp" "k8s.io/apimachinery/pkg/util/yaml" @@ -1371,17 +1372,29 @@ func TestCreateResources(t *testing.T) { process := testenv.MakeTestServer(t, testenv.WithLogger(utils.NewSlogLoggerForTests())) rootClient := testenv.MakeDefaultAuthClient(t, process) + // tctlGetAllValidations allows tests to register post-test validations to validate + // that their resource is present in "tctl get all" output. + // This allows running test rows instead of the whole test table. + var tctlGetAllValidations []func(t *testing.T, out string) + tests := []struct { - kind string - create func(t *testing.T, clt *authclient.Client) + kind string + create func(t *testing.T, clt *authclient.Client) + getAllCheck func(t *testing.T, out string) }{ { kind: types.KindGithubConnector, create: testCreateGithubConnector, + getAllCheck: func(t *testing.T, s string) { + assert.Contains(t, s, "kind: github") + }, }, { kind: types.KindRole, create: testCreateRole, + getAllCheck: func(t *testing.T, s string) { + assert.Contains(t, s, "kind: role") + }, }, { kind: types.KindServerInfo, @@ -1390,6 +1403,9 @@ func TestCreateResources(t *testing.T) { { kind: types.KindUser, create: testCreateUser, + getAllCheck: func(t *testing.T, s string) { + assert.Contains(t, s, "kind: user") + }, }, { kind: types.KindDatabaseObjectImportRule, @@ -1402,10 +1418,16 @@ func TestCreateResources(t *testing.T) { { kind: types.KindClusterNetworkingConfig, create: testCreateClusterNetworkingConfig, + getAllCheck: func(t *testing.T, s string) { + assert.Contains(t, s, "kind: cluster_networking_config") + }, }, { kind: types.KindClusterAuthPreference, create: testCreateAuthPreference, + getAllCheck: func(t *testing.T, s string) { + assert.Contains(t, s, "kind: cluster_auth_preference") + }, }, { kind: types.KindSessionRecordingConfig, @@ -1440,6 +1462,9 @@ func TestCreateResources(t *testing.T) { for _, test := range tests { t.Run(test.kind, func(t *testing.T) { test.create(t, rootClient) + if test.getAllCheck != nil { + tctlGetAllValidations = append(tctlGetAllValidations, test.getAllCheck) + } }) } @@ -1447,12 +1472,9 @@ func TestCreateResources(t *testing.T) { out, err := runResourceCommand(t, rootClient, []string{"get", "all"}) require.NoError(t, err) s := out.String() - require.NotEmpty(t, s) - assert.Contains(t, s, "kind: github") - assert.Contains(t, s, "kind: cluster_auth_preference") - assert.Contains(t, s, "kind: cluster_networking_config") - assert.Contains(t, s, "kind: user") - assert.Contains(t, s, "kind: role") + for _, validateGetAll := range tctlGetAllValidations { + validateGetAll(t, s) + } } func testCreateGithubConnector(t *testing.T, clt *authclient.Client) { @@ -2326,18 +2348,21 @@ version: v1 _, err = runResourceCommand(t, clt, []string{"create", resourceYAMLPath}) require.NoError(t, err) - // Get the resource buf, err := runResourceCommand(t, clt, []string{"get", types.KindAutoUpdateConfig, "--format=json"}) require.NoError(t, err) - resources := mustDecodeJSON[[]*autoupdate.AutoUpdateConfig](t, buf) - require.Len(t, resources, 1) + + rawResources := mustDecodeJSON[[]services.UnknownResource](t, buf) + require.Len(t, rawResources, 1) + var resource autoupdate.AutoUpdateConfig + require.NoError(t, protojson.Unmarshal(rawResources[0].Raw, &resource)) var expected autoupdate.AutoUpdateConfig - require.NoError(t, yaml.Unmarshal([]byte(resourceYAML), &expected)) + expectedJSON := mustTranscodeYAMLToJSON(t, bytes.NewReader([]byte(resourceYAML))) + require.NoError(t, protojson.Unmarshal(expectedJSON, &expected)) require.Empty(t, cmp.Diff( - []*autoupdate.AutoUpdateConfig{&expected}, - resources, + &expected, + &resource, protocmp.IgnoreFields(&headerv1.Metadata{}, "revision"), protocmp.Transform(), )) @@ -2368,18 +2393,21 @@ version: v1 _, err = runResourceCommand(t, clt, []string{"create", resourceYAMLPath}) require.NoError(t, err) - // Get the resource buf, err := runResourceCommand(t, clt, []string{"get", types.KindAutoUpdateVersion, "--format=json"}) require.NoError(t, err) - resources := mustDecodeJSON[[]*autoupdate.AutoUpdateVersion](t, buf) - require.Len(t, resources, 1) + + rawResources := mustDecodeJSON[[]services.UnknownResource](t, buf) + require.Len(t, rawResources, 1) + var resource autoupdate.AutoUpdateVersion + require.NoError(t, protojson.Unmarshal(rawResources[0].Raw, &resource)) var expected autoupdate.AutoUpdateVersion - require.NoError(t, yaml.Unmarshal([]byte(resourceYAML), &expected)) + expectedJSON := mustTranscodeYAMLToJSON(t, bytes.NewReader([]byte(resourceYAML))) + require.NoError(t, protojson.Unmarshal(expectedJSON, &expected)) require.Empty(t, cmp.Diff( - []*autoupdate.AutoUpdateVersion{&expected}, - resources, + &expected, + &resource, protocmp.IgnoreFields(&headerv1.Metadata{}, "revision"), protocmp.Transform(), )) @@ -2423,15 +2451,19 @@ version: v1 // Get the resource buf, err := runResourceCommand(t, clt, []string{"get", types.KindAutoUpdateAgentRollout, "--format=json"}) require.NoError(t, err) - resources := mustDecodeJSON[[]*autoupdate.AutoUpdateAgentRollout](t, buf) - require.Len(t, resources, 1) + + rawResources := mustDecodeJSON[[]services.UnknownResource](t, buf) + require.Len(t, rawResources, 1) + var resource autoupdate.AutoUpdateAgentRollout + require.NoError(t, protojson.Unmarshal(rawResources[0].Raw, &resource)) var expected autoupdate.AutoUpdateAgentRollout - require.NoError(t, yaml.Unmarshal([]byte(resourceYAML), &expected)) + expectedJSON := mustTranscodeYAMLToJSON(t, bytes.NewReader([]byte(resourceYAML))) + require.NoError(t, protojson.Unmarshal(expectedJSON, &expected)) require.Empty(t, cmp.Diff( - []*autoupdate.AutoUpdateAgentRollout{&expected}, - resources, + &expected, + &resource, protocmp.IgnoreFields(&headerv1.Metadata{}, "revision"), protocmp.Transform(), )) diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 8318fcf68a45f..15e96fc949346 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -1014,10 +1014,22 @@ func dumpConfigFile(outputURI, contents, comment string) (string, error) { // user's privileges // // This is the entry point of "teleport scp" call (the parent process is the teleport daemon) -func onSCP(scpFlags *scp.Flags) (err error) { +func onSCP(scpFlags *scp.Flags) error { // when 'teleport scp' is executed, it cannot write logs to stderr (because // they're automatically replayed by the scp client) - utils.SwitchLoggingToSyslog() + var verbosity string + if scpFlags.Verbose { + verbosity = teleport.DebugLevel + } + _, _, err := logutils.Initialize(logutils.Config{ + Output: teleport.Syslog, + Severity: verbosity, + }) + if err != nil { + // If something went wrong, discard all logs and continue command execution. + slog.SetDefault(slog.New(logutils.DiscardHandler{})) + } + if len(scpFlags.Target) == 0 { return trace.BadParameter("teleport scp: missing an argument") } diff --git a/tool/teleport/testenv/test_server.go b/tool/teleport/testenv/test_server.go index 3e034d9fdf0d6..e4c9245c478ce 100644 --- a/tool/teleport/testenv/test_server.go +++ b/tool/teleport/testenv/test_server.go @@ -150,7 +150,7 @@ func MakeTestServer(t *testing.T, opts ...TestServerOptFunc) (process *service.T cfg.Hostname = "server01" cfg.DataDir = t.TempDir() - cfg.Log = utils.NewLoggerForTests() + cfg.Logger = utils.NewSlogLoggerForTests() authAddr := utils.NetAddr{AddrNetwork: "tcp", Addr: NewTCPListener(t, service.ListenerAuth, &cfg.FileDescriptors)} cfg.SetToken(StaticToken) cfg.SetAuthServerAddress(authAddr) diff --git a/tool/tsh/common/git.go b/tool/tsh/common/git.go index 3f43578fb4132..990ddb8f22fc7 100644 --- a/tool/tsh/common/git.go +++ b/tool/tsh/common/git.go @@ -19,24 +19,128 @@ package common import ( + "bytes" + "io" + "os/exec" + "strings" + "github.com/alecthomas/kingpin/v2" + "github.com/go-git/go-git/v5/plumbing/transport" + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/types" ) type gitCommands struct { - list *gitListCommand - login *gitLoginCommand + list *gitListCommand + login *gitLoginCommand + ssh *gitSSHCommand + config *gitConfigCommand + clone *gitCloneCommand } func newGitCommands(app *kingpin.Application) gitCommands { git := app.Command("git", "Git server commands.") cmds := gitCommands{ - login: newGitLoginCommand(git), - list: newGitListCommand(git), + login: newGitLoginCommand(git), + list: newGitListCommand(git), + ssh: newGitSSHCommand(git), + config: newGitConfigCommand(git), + clone: newGitCloneCommand(git), } // TODO(greedy52) hide the commands until all basic features are implemented. git.Hidden() cmds.login.Hidden() cmds.list.Hidden() + cmds.config.Hidden() + cmds.clone.Hidden() return cmds } + +type gitSSHURL transport.Endpoint + +func (g gitSSHURL) check() error { + switch { + case g.isGitHub(): + if err := types.ValidateGitHubOrganizationName(g.owner()); err != nil { + return trace.Wrap(err) + } + } + return nil +} + +func (g gitSSHURL) isGitHub() bool { + return g.Host == "github.com" +} + +// owner returns the first part of the path. If the path does not have an owner, +// an empty string is returned. +// +// For GitHub, owner is either the user or the organization that owns the repo. +// +// For example, if the SSH url is git@github.com:gravitational/teleport.git, the +// owner would be "gravitational". +func (g gitSSHURL) owner() string { + // g.Path may have a preceding "/" from url.Parse. + owner, _, ok := strings.Cut(strings.TrimPrefix(g.Path, "/"), "/") + if !ok { + return "" + } + return owner +} + +// parseGitSSHURL parse a Git SSH URL. +// +// Git URL Spec: +// - spec: https://git-scm.com/docs/git-clone#_git_urls +// - example: ssh://example.org/path/to/repo.git +// +// GitHub (SCP-like) URL: +// - spec: https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories +// - example: git@github.com:gravitational/teleport.git +func parseGitSSHURL(originalURL string) (*gitSSHURL, error) { + endpoint, err := transport.NewEndpoint(originalURL) + if err != nil { + return nil, trace.Wrap(err) + } + if endpoint.Protocol != "ssh" { + return nil, trace.BadParameter("unsupported git ssh URL %s", originalURL) + } + s := gitSSHURL(*endpoint) + if err := s.check(); err != nil { + return nil, trace.Wrap(err) + } + return &s, nil +} + +func execGitAndCaptureStdout(cf *CLIConf, args ...string) (string, error) { + var bufStd bytes.Buffer + if err := execGitWithStdoutAndStderr(cf, &bufStd, cf.Stderr(), args...); err != nil { + return "", trace.Wrap(err) + } + return strings.TrimSpace(bufStd.String()), nil +} + +func execGit(cf *CLIConf, args ...string) error { + return trace.Wrap(execGitWithStdoutAndStderr(cf, cf.Stdout(), cf.Stderr(), args...)) +} + +func execGitWithStdoutAndStderr(cf *CLIConf, stdout, stderr io.Writer, args ...string) error { + const gitExecutable = "git" + gitPath, err := cf.LookPath(gitExecutable) + if err != nil { + return trace.NotFound(`could not locate the executable %q. The following error occurred: +%s + +tsh requires that the %q executable to be installed. +You can install it by following the instructions at https://git-scm.com/book/en/v2/Getting-Started-Installing-Git`, + gitExecutable, err.Error(), gitExecutable) + } + logger.DebugContext(cf.Context, "Executing git command", "path", gitPath, "args", args) + cmd := exec.CommandContext(cf.Context, gitPath, args...) + cmd.Stdin = cf.Stdin() + cmd.Stdout = stdout + cmd.Stderr = stderr + return trace.Wrap(cf.RunCommand(cmd)) +} diff --git a/tool/tsh/common/git_clone.go b/tool/tsh/common/git_clone.go new file mode 100644 index 0000000000000..93d00d4134434 --- /dev/null +++ b/tool/tsh/common/git_clone.go @@ -0,0 +1,73 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package common + +import ( + "fmt" + + "github.com/alecthomas/kingpin/v2" + "github.com/gravitational/trace" +) + +// gitCloneCommand implements `tsh git clone`. +// +// This command internally executes `git clone` while setting `core.sshcommand`. +// You can generally assume the user has `git` binary installed (otherwise there +// is no point using the `git` proxy feature). +// +// TODO(greedy52) investigate using `go-git` library instead of calling `git +// clone`. +type gitCloneCommand struct { + *kingpin.CmdClause + + repository string + directory string +} + +func newGitCloneCommand(parent *kingpin.CmdClause) *gitCloneCommand { + cmd := &gitCloneCommand{ + CmdClause: parent.Command("clone", "Clone a Git repository."), + } + + cmd.Arg("repository", "Git URL of the repository to clone.").Required().StringVar(&cmd.repository) + cmd.Arg("directory", "The name of a new directory to clone into.").StringVar(&cmd.directory) + // TODO(greedy52) support passing extra args to git like --branch/--depth. + return cmd +} + +func (c *gitCloneCommand) run(cf *CLIConf) error { + u, err := parseGitSSHURL(c.repository) + if err != nil { + return trace.Wrap(err) + } + if !u.isGitHub() { + return trace.BadParameter("%s is not a GitHub repository", c.repository) + } + + sshCommand := makeGitCoreSSHCommand(cf.executablePath, u.owner()) + args := []string{ + "clone", + "--config", fmt.Sprintf("%s=%s", gitCoreSSHCommand, sshCommand), + c.repository, + } + if c.directory != "" { + args = append(args, c.directory) + } + return trace.Wrap(execGit(cf, args...)) +} diff --git a/tool/tsh/common/git_clone_test.go b/tool/tsh/common/git_clone_test.go new file mode 100644 index 0000000000000..4e27e3ac3286f --- /dev/null +++ b/tool/tsh/common/git_clone_test.go @@ -0,0 +1,115 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package common + +import ( + "context" + "os/exec" + "slices" + "testing" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" +) + +func TestGitCloneCommand(t *testing.T) { + tests := []struct { + name string + cmd *gitCloneCommand + verifyCommand func(*exec.Cmd) error + checkError require.ErrorAssertionFunc + }{ + { + name: "success", + cmd: &gitCloneCommand{ + repository: "git@github.com:gravitational/teleport.git", + }, + verifyCommand: func(cmd *exec.Cmd) error { + expect := []string{ + "git", "clone", + "--config", "core.sshcommand=\"tsh\" git ssh --github-org gravitational", + "git@github.com:gravitational/teleport.git", + } + if !slices.Equal(expect, cmd.Args) { + return trace.CompareFailed("expect %v but got %v", expect, cmd.Args) + } + return nil + }, + checkError: require.NoError, + }, + { + name: "success with target dir", + cmd: &gitCloneCommand{ + repository: "git@github.com:gravitational/teleport.git", + directory: "target_dir", + }, + verifyCommand: func(cmd *exec.Cmd) error { + expect := []string{ + "git", "clone", + "--config", "core.sshcommand=\"tsh\" git ssh --github-org gravitational", + "git@github.com:gravitational/teleport.git", + "target_dir", + } + if !slices.Equal(expect, cmd.Args) { + return trace.CompareFailed("expect %v but got %v", expect, cmd.Args) + } + return nil + }, + checkError: require.NoError, + }, + { + name: "invalid URL", + cmd: &gitCloneCommand{ + repository: "not-a-git-ssh-url", + }, + checkError: require.Error, + }, + { + name: "unsupported Git service", + cmd: &gitCloneCommand{ + repository: "git@gitlab.com:group/project.git", + }, + checkError: require.Error, + }, + { + name: "git fails", + cmd: &gitCloneCommand{ + repository: "git@github.com:gravitational/teleport.git", + }, + verifyCommand: func(cmd *exec.Cmd) error { + return trace.BadParameter("some git error") + }, + checkError: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorIs(t, err, trace.BadParameter("some git error")) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cf := &CLIConf{ + Context: context.Background(), + executablePath: "tsh", + cmdRunner: tt.verifyCommand, + lookPathOverride: "git", + } + tt.checkError(t, tt.cmd.run(cf)) + }) + } +} diff --git a/tool/tsh/common/git_config.go b/tool/tsh/common/git_config.go new file mode 100644 index 0000000000000..89771735b30b3 --- /dev/null +++ b/tool/tsh/common/git_config.go @@ -0,0 +1,184 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package common + +import ( + "fmt" + "io" + "strings" + + "github.com/alecthomas/kingpin/v2" + "github.com/gravitational/trace" +) + +// gitConfigCommand implements `tsh git config`. +// +// This command internally executes `git` commands like `git config xxx`. +// can generally assume the user has `git` binary installed (otherwise there is +// no point using the `git` proxy feature). +// +// TODO(greedy52) investigate using `go-git` library instead of calling `git +// config`. +type gitConfigCommand struct { + *kingpin.CmdClause + + action string +} + +const ( + gitConfigActionDefault = "" + gitConfigActionUpdate = "update" + gitConfigActionReset = "reset" + + // gitCoreSSHCommand is the Git config used for setting up alternative SSH + // command. For Git-proxying, the command should point to "tsh git ssh". + // + // https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresshCommand + gitCoreSSHCommand = "core.sshcommand" +) + +func newGitConfigCommand(parent *kingpin.CmdClause) *gitConfigCommand { + cmd := &gitConfigCommand{ + CmdClause: parent.Command("config", "Check Teleport config on the working Git directory. Or provide an action ('update' or 'reset') to configure the Git repo."), + } + + cmd.Arg("action", "Optional action to perform. 'update' to configure the Git repo to proxy Git commands through Teleport. 'reset' to clear Teleport configuration from the Git repo."). + EnumVar(&cmd.action, gitConfigActionUpdate, gitConfigActionReset) + return cmd +} + +func (c *gitConfigCommand) run(cf *CLIConf) error { + // Make sure we are in a Git dir. + err := execGitWithStdoutAndStderr(cf, io.Discard, io.Discard, "rev-parse", "--is-inside-work-tree") + if err != nil { + // In case git is not found, return the look path error. + if trace.IsNotFound(err) { + return trace.Wrap(err) + } + // This error message is a slight alternation of the original error + // message from the above command. + return trace.BadParameter("the current directory is not a Git repository (or any of the parent directories)") + } + + switch c.action { + case gitConfigActionDefault: + return trace.Wrap(c.doCheck(cf)) + case gitConfigActionUpdate: + return trace.Wrap(c.doUpdate(cf)) + case gitConfigActionReset: + return trace.Wrap(c.doReset(cf)) + default: + return trace.BadParameter("unknown action '%v'", c.action) + } +} + +func (c *gitConfigCommand) doCheck(cf *CLIConf) error { + sshCommand, err := c.getCoreSSHCommand(cf) + if err != nil { + return trace.Wrap(err) + } + wantPrefix := makeGitCoreSSHCommand(cf.executablePath, "") + if strings.HasPrefix(sshCommand, wantPrefix) { + _, org, _ := strings.Cut(sshCommand, wantPrefix) + fmt.Fprintf(cf.Stdout(), "The current Git directory is configured with Teleport for GitHub organization %q.\n", org) + return nil + } + + c.printDirNotConfigured(cf.Stdout(), true, sshCommand) + return nil +} + +func (c *gitConfigCommand) printDirNotConfigured(w io.Writer, withUpdate bool, existingSSHCommand string) { + fmt.Fprintln(w, "The current Git directory is not configured with Teleport.") + if withUpdate { + if existingSSHCommand != "" { + fmt.Fprintf(w, "%q currently has value %q.\n", gitCoreSSHCommand, existingSSHCommand) + fmt.Fprintf(w, "Run 'tsh git config update' to configure Git directory with Teleport but %q will be overwritten.\n", gitCoreSSHCommand) + } else { + fmt.Fprintln(w, "Run 'tsh git config update' to configure it.") + } + } +} + +func (c *gitConfigCommand) doUpdate(cf *CLIConf) error { + urls, err := execGitAndCaptureStdout(cf, "ls-remote", "--get-url") + if err != nil { + return trace.Wrap(err) + } + for _, url := range strings.Split(urls, "\n") { + u, err := parseGitSSHURL(url) + if err != nil { + logger.DebugContext(cf.Context, "Skippig URL", "error", err, "url", url) + continue + } + if !u.isGitHub() { + logger.DebugContext(cf.Context, "Skippig non-GitHub host", "host", u.Host) + continue + } + + logger.DebugContext(cf.Context, "Configuring repo to use tsh.", "url", url, "owner", u.owner()) + args := []string{ + "config", "--local", + "--replace-all", gitCoreSSHCommand, + makeGitCoreSSHCommand(cf.executablePath, u.owner()), + } + if err := execGit(cf, args...); err != nil { + return trace.Wrap(err) + } + fmt.Fprintln(cf.Stdout(), "Teleport configuration added.") + return trace.Wrap(c.doCheck(cf)) + } + return trace.NotFound("no GitHub SSH URL found from 'git ls-remote --get-url'") +} + +func (c *gitConfigCommand) doReset(cf *CLIConf) error { + sshCommand, err := c.getCoreSSHCommand(cf) + if err != nil { + return trace.Wrap(err) + } + wantPrefix := makeGitCoreSSHCommand(cf.executablePath, "") + if !strings.HasPrefix(sshCommand, wantPrefix) { + c.printDirNotConfigured(cf.Stdout(), false, sshCommand) + return nil + } + + if err := execGit(cf, "config", "--local", "--unset-all", gitCoreSSHCommand); err != nil { + return trace.Wrap(err) + } + fmt.Fprintln(cf.Stdout(), "Teleport configuration removed.") + return nil +} + +func (c *gitConfigCommand) getCoreSSHCommand(cf *CLIConf) (string, error) { + return execGitAndCaptureStdout(cf, + "config", "--local", + // set default to empty to avoid non-zero exit when config is missing + "--default", "", + "--get", gitCoreSSHCommand, + ) +} + +// makeGitCoreSSHCommand generates the value for Git config "core.sshcommand". +func makeGitCoreSSHCommand(tshBin, githubOrg string) string { + // Quote the path in case it has spaces + return fmt.Sprintf("\"%s\" git ssh --github-org %s", + tshBin, + githubOrg, + ) +} diff --git a/tool/tsh/common/git_config_test.go b/tool/tsh/common/git_config_test.go new file mode 100644 index 0000000000000..b045e9342bb5f --- /dev/null +++ b/tool/tsh/common/git_config_test.go @@ -0,0 +1,184 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package common + +import ( + "bytes" + "context" + "fmt" + "os/exec" + "slices" + "testing" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" +) + +func isGitDirCheck(cmd *exec.Cmd) bool { + return slices.Equal([]string{"git", "rev-parse", "--is-inside-work-tree"}, cmd.Args) +} +func isGitListRemoteURL(cmd *exec.Cmd) bool { + return slices.Equal([]string{"git", "ls-remote", "--get-url"}, cmd.Args) +} +func isGitConfigGetCoreSSHCommand(cmd *exec.Cmd) bool { + return slices.Equal([]string{"git", "config", "--local", "--default", "", "--get", "core.sshcommand"}, cmd.Args) +} + +type fakeGitCommandRunner struct { + dirCheckError error + coreSSHCommand string + remoteURL string + verifyCommand func(cmd *exec.Cmd) error +} + +func (f fakeGitCommandRunner) run(cmd *exec.Cmd) error { + switch { + case isGitDirCheck(cmd): + return f.dirCheckError + case isGitConfigGetCoreSSHCommand(cmd): + fmt.Fprintln(cmd.Stdout, f.coreSSHCommand) + return nil + case isGitListRemoteURL(cmd): + fmt.Fprintln(cmd.Stdout, f.remoteURL) + return nil + default: + if f.verifyCommand != nil { + return trace.Wrap(f.verifyCommand(cmd)) + } + return trace.NotFound("unknown command") + } +} + +func TestGitConfigCommand(t *testing.T) { + tests := []struct { + name string + cmd *gitConfigCommand + fakeRunner fakeGitCommandRunner + checkError require.ErrorAssertionFunc + checkOutputContains string + }{ + { + name: "not a git dir", + cmd: &gitConfigCommand{}, + fakeRunner: fakeGitCommandRunner{ + dirCheckError: trace.BadParameter("not a git dir"), + }, + checkError: func(t require.TestingT, err error, i ...interface{}) { + require.Error(t, err) + require.Contains(t, err.Error(), "the current directory is not a Git repository") + }, + }, + { + name: "check", + cmd: &gitConfigCommand{}, + fakeRunner: fakeGitCommandRunner{ + coreSSHCommand: makeGitCoreSSHCommand("tsh", "org"), + }, + checkError: require.NoError, + checkOutputContains: "is configured with Teleport for GitHub organization \"org\"", + }, + { + name: "check not configured", + cmd: &gitConfigCommand{}, + fakeRunner: fakeGitCommandRunner{ + coreSSHCommand: "", + }, + checkError: require.NoError, + checkOutputContains: "is not configured", + }, + { + name: "update success", + cmd: &gitConfigCommand{ + action: gitConfigActionUpdate, + }, + fakeRunner: fakeGitCommandRunner{ + coreSSHCommand: makeGitCoreSSHCommand("tsh", "org"), + remoteURL: "git@github.com:gravitational/teleport.git", + verifyCommand: func(cmd *exec.Cmd) error { + expect := []string{ + "git", "config", "--local", + "--replace-all", "core.sshcommand", + "\"tsh\" git ssh --github-org gravitational", + } + if !slices.Equal(expect, cmd.Args) { + return trace.CompareFailed("expect %v but got %v", expect, cmd.Args) + } + return nil + }, + }, + checkError: require.NoError, + }, + { + name: "update failed missing url", + cmd: &gitConfigCommand{ + action: gitConfigActionUpdate, + }, + fakeRunner: fakeGitCommandRunner{ + coreSSHCommand: makeGitCoreSSHCommand("tsh", "org"), + remoteURL: "", + }, + checkError: require.Error, + }, + { + name: "reset no-op", + cmd: &gitConfigCommand{ + action: gitConfigActionReset, + }, + fakeRunner: fakeGitCommandRunner{ + coreSSHCommand: "", + }, + checkError: require.NoError, + }, + { + name: "reset no-op", + cmd: &gitConfigCommand{ + action: gitConfigActionReset, + }, + fakeRunner: fakeGitCommandRunner{ + coreSSHCommand: makeGitCoreSSHCommand("tsh", "org"), + verifyCommand: func(cmd *exec.Cmd) error { + expect := []string{ + "git", "config", "--local", + "--unset-all", "core.sshcommand", + } + if !slices.Equal(expect, cmd.Args) { + return trace.CompareFailed("expect %v but got %v", expect, cmd.Args) + } + return nil + }, + }, + checkError: require.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + cf := &CLIConf{ + Context: context.Background(), + OverrideStdout: &buf, + executablePath: "tsh", + cmdRunner: tt.fakeRunner.run, + lookPathOverride: "git", + } + tt.checkError(t, tt.cmd.run(cf)) + require.Contains(t, buf.String(), tt.checkOutputContains) + }) + } +} diff --git a/tool/tsh/common/git_ssh.go b/tool/tsh/common/git_ssh.go new file mode 100644 index 0000000000000..d4221d0f2f286 --- /dev/null +++ b/tool/tsh/common/git_ssh.go @@ -0,0 +1,86 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package common + +import ( + "fmt" + "os" + "strings" + + "github.com/alecthomas/kingpin/v2" + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/client" +) + +// gitSSHCommand implements `tsh git ssh`. +// +// Note that this is a hidden command as it is only meant for 'git` to call. +// TODO(greedy52) support Git protocol v2. +type gitSSHCommand struct { + *kingpin.CmdClause + + gitHubOrg string + userHost string + command []string + options []string +} + +func newGitSSHCommand(parent *kingpin.CmdClause) *gitSSHCommand { + cmd := &gitSSHCommand{ + CmdClause: parent.Command("ssh", "Proxy Git commands using SSH").Hidden(), + } + + cmd.Flag("github-org", "GitHub organization.").Required().StringVar(&cmd.gitHubOrg) + cmd.Arg("[user@]host", "Remote hostname and the login to use").Required().StringVar(&cmd.userHost) + cmd.Arg("command", "Command to execute on a remote host").StringsVar(&cmd.command) + cmd.Flag("option", "OpenSSH options in the format used in the configuration file").Short('o').AllowDuplicate().StringsVar(&cmd.options) + return cmd +} + +func (c *gitSSHCommand) run(cf *CLIConf) error { + _, host, ok := strings.Cut(c.userHost, "@") + if !ok || host != "github.com" { + return trace.BadParameter("user-host %q is not GitHub", c.userHost) + } + + // TODO(greedy52) when git calls tsh, tsh cannot prompt for password (e.g. + // user session expired) using provided stdin pipe. `tc.Login` should try + // hijacking "/dev/tty" and replace `prompt.Stdin` temporarily. + identity, err := getGitHubIdentity(cf, c.gitHubOrg) + if err != nil { + return trace.Wrap(err) + } + logger.DebugContext(cf.Context, "Proxying git command for GitHub user.", "command", c.command, "user", identity.Username) + + cf.RemoteCommand = c.command + cf.Options = c.options + cf.UserHost = fmt.Sprintf("git@%s", types.MakeGitHubOrgServerDomain(c.gitHubOrg)) + + tc, err := makeClient(cf) + if err != nil { + return trace.Wrap(err) + } + tc.Stdin = os.Stdin + err = client.RetryWithRelogin(cf.Context, tc, func() error { + return tc.SSH(cf.Context, cf.RemoteCommand) + }) + return trace.Wrap(convertSSHExitCode(tc, err)) +} diff --git a/tool/tsh/common/git_test.go b/tool/tsh/common/git_test.go new file mode 100644 index 0000000000000..501004abd141d --- /dev/null +++ b/tool/tsh/common/git_test.go @@ -0,0 +1,79 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package common + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_parseGitSSHURL(t *testing.T) { + tests := []struct { + name string + input string + wantError bool + wantOut *gitSSHURL + }{ + { + name: "github ssh format", + input: "org-1234567@github.com:some-org/some-repo.git", + wantOut: &gitSSHURL{ + Protocol: "ssh", + Host: "github.com", + User: "org-1234567", + Path: "some-org/some-repo.git", + Port: 22, + }, + }, + { + name: "github ssh format invalid path", + input: "org-1234567@github.com:missing-org", + wantError: true, + }, + { + name: "ssh schema format", + input: "ssh://git@github.com/some-org/some-repo.git", + wantOut: &gitSSHURL{ + Protocol: "ssh", + Host: "github.com", + User: "git", + Path: "/some-org/some-repo.git", + }, + }, + { + name: "unsupported format", + input: "https://github.com/gravitational/teleport.git", + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + out, err := parseGitSSHURL(tt.input) + t.Log(out, err) + if tt.wantError { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.wantOut, out) + }) + } +} diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index d65af0b7247db..7677a6a842251 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -575,6 +575,9 @@ type CLIConf struct { // profileStatusOverride overrides return of ProfileStatus(). used in tests. profileStatusOverride *client.ProfileStatus + + // lookPathOverride overrides return of LookPath(). used in tests. + lookPathOverride string } // Stdout returns the stdout writer. @@ -614,6 +617,14 @@ func (c *CLIConf) RunCommand(cmd *exec.Cmd) error { return trace.Wrap(cmd.Run()) } +// LookPath searches for an executable named file. +func (c *CLIConf) LookPath(file string) (string, error) { + if c.lookPathOverride != "" { + return c.lookPathOverride, nil + } + return exec.LookPath(file) +} + func Main() { cmdLineOrig := os.Args[1:] var cmdLine []string @@ -1637,6 +1648,12 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error { err = gitCmd.list.run(&cf) case gitCmd.login.FullCommand(): err = gitCmd.login.run(&cf) + case gitCmd.ssh.FullCommand(): + err = gitCmd.ssh.run(&cf) + case gitCmd.config.FullCommand(): + err = gitCmd.config.run(&cf) + case gitCmd.clone.FullCommand(): + err = gitCmd.clone.run(&cf) default: // Handle commands that might not be available. switch { @@ -3969,7 +3986,12 @@ func onSSH(cf *CLIConf) error { accessRequestForSSH, fmt.Sprintf("%s@%s", tc.HostLogin, tc.Host), ) + // Exit with the same exit status as the failed command. + return trace.Wrap(convertSSHExitCode(tc, err)) +} + +func convertSSHExitCode(tc *client.TeleportClient, err error) error { if tc.ExitStatus != 0 { var exitErr *common.ExitCodeError if errors.As(err, &exitErr) { diff --git a/tool/tsh/common/tsh_helper_test.go b/tool/tsh/common/tsh_helper_test.go index 85ec86097b3bd..35250d6f54e4b 100644 --- a/tool/tsh/common/tsh_helper_test.go +++ b/tool/tsh/common/tsh_helper_test.go @@ -97,7 +97,7 @@ func (s *suite) setupRootCluster(t *testing.T, options testSuiteOptions) { cfg := servicecfg.MakeDefaultConfig() cfg.CircuitBreakerConfig = breaker.NoopBreakerConfig() - cfg.Log = utils.NewLoggerForTests() + cfg.Logger = utils.NewSlogLoggerForTests() err := config.ApplyFileConfig(fileConfig, cfg) require.NoError(t, err) cfg.FileDescriptors = dynAddr.Descriptors @@ -194,7 +194,7 @@ func (s *suite) setupLeafCluster(t *testing.T, options testSuiteOptions) { cfg := servicecfg.MakeDefaultConfig() cfg.CircuitBreakerConfig = breaker.NoopBreakerConfig() - cfg.Log = utils.NewLoggerForTests() + cfg.Logger = utils.NewSlogLoggerForTests() err := config.ApplyFileConfig(fileConfig, cfg) require.NoError(t, err) cfg.FileDescriptors = dynAddr.Descriptors diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index 2ffa313d42cb1..61dda435b127d 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -3856,7 +3856,7 @@ func makeTestSSHNode(t *testing.T, authAddr *utils.NetAddr, opts ...testServerOp cfg.SSH.Addr = *utils.MustParseAddr("127.0.0.1:0") cfg.SSH.PublicAddrs = []utils.NetAddr{cfg.SSH.Addr} cfg.SSH.DisableCreateHostUser = true - cfg.Log = utils.NewLoggerForTests() + cfg.Logger = utils.NewSlogLoggerForTests() // Disabling debug service for tests so that it doesn't break if the data // directory path is too long. cfg.DebugService.Enabled = false @@ -3905,7 +3905,7 @@ func makeTestServers(t *testing.T, opts ...testServerOptFunc) (auth *service.Tel cfg.Proxy.SSHAddr = utils.NetAddr{AddrNetwork: "tcp", Addr: net.JoinHostPort("127.0.0.1", ports.Pop())} cfg.Proxy.ReverseTunnelListenAddr = utils.NetAddr{AddrNetwork: "tcp", Addr: net.JoinHostPort("127.0.0.1", ports.Pop())} cfg.Proxy.DisableWebInterface = true - cfg.Log = utils.NewLoggerForTests() + cfg.Logger = utils.NewSlogLoggerForTests() // Disabling debug service for tests so that it doesn't break if the data // directory path is too long. cfg.DebugService.Enabled = false diff --git a/web/packages/design/src/Icon/Icon.story.tsx b/web/packages/design/src/Icon/Icon.story.tsx new file mode 100644 index 0000000000000..87377283a0869 --- /dev/null +++ b/web/packages/design/src/Icon/Icon.story.tsx @@ -0,0 +1,51 @@ +/** + * Teleport + * Copyright (C) 2025 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { useEffect, useRef } from 'react'; +import styled from 'styled-components'; + +import { Flex } from 'design'; +import { blink } from 'design/keyframes'; + +import { Broadcast } from './Icons/Broadcast'; + +export default { + // Nest stories under Icon/Icon, so that Icon/Icons which lists all icons is the first story. + title: 'Design/Icon/Icon', +}; + +export const WithRef = () => { + const nodeRef = useRef(null); + + useEffect(() => { + nodeRef.current?.scrollIntoView({ block: 'center' }); + }, []); + + return ( + + + + On the first render, the view should be scrolled to the icon. + + + ); +}; + +const StyledBroadcast = styled(Broadcast)` + animation: ${blink} 1s ease-in-out infinite; +`; diff --git a/web/packages/design/src/Icon/Icon.tsx b/web/packages/design/src/Icon/Icon.tsx index 511bc47e93dc8..5d62c65cd5748 100644 --- a/web/packages/design/src/Icon/Icon.tsx +++ b/web/packages/design/src/Icon/Icon.tsx @@ -16,42 +16,40 @@ * along with this program. If not, see . */ -import React, { PropsWithChildren } from 'react'; +import React, { ForwardedRef, forwardRef, PropsWithChildren } from 'react'; import styled from 'styled-components'; import { borderRadius, color, space, SpaceProps } from 'design/system'; -export function Icon({ - size = 'medium', - children, - ...otherProps -}: PropsWithChildren) { - let iconSize = size; - if (size === 'small') { - iconSize = 16; +export const Icon = forwardRef>( + ({ size = 'medium', children, ...otherProps }, ref) => { + let iconSize = size; + if (size === 'small') { + iconSize = 16; + } + if (size === 'medium') { + iconSize = 20; + } + if (size === 'large') { + iconSize = 24; + } + if (size === 'extra-large') { + iconSize = 32; + } + return ( + + + {children} + + + ); } - if (size === 'medium') { - iconSize = 20; - } - if (size === 'large') { - iconSize = 24; - } - if (size === 'extra-large') { - iconSize = 32; - } - return ( - - - {children} - - - ); -} +); const StyledIcon = styled.span` display: inline-flex; @@ -65,6 +63,9 @@ const StyledIcon = styled.span` export type IconSize = 'small' | 'medium' | 'large' | 'extra-large' | number; +/** + * IconProps are used in each autogenerated icon component. + */ export type IconProps = SpaceProps & { size?: IconSize; color?: string; @@ -79,7 +80,11 @@ export type IconProps = SpaceProps & { className?: string; }; +/** + * Props are used on the Icon component, but not in autogenerated icon components. + */ type Props = IconProps & { children?: React.SVGProps | React.SVGProps[]; a?: any; + ref?: ForwardedRef; }; diff --git a/web/packages/design/src/Icon/Icons/Add.tsx b/web/packages/design/src/Icon/Icons/Add.tsx index 67a9d045f54bf..5c6ed4a2f4055 100644 --- a/web/packages/design/src/Icon/Icons/Add.tsx +++ b/web/packages/design/src/Icon/Icons/Add.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,10 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Add({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Add = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/AddCircle.tsx b/web/packages/design/src/Icon/Icons/AddCircle.tsx index a45873ed1cdc5..05a61f6a7d4cd 100644 --- a/web/packages/design/src/Icon/Icons/AddCircle.tsx +++ b/web/packages/design/src/Icon/Icons/AddCircle.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function AddCircle({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const AddCircle = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/AddUsers.tsx b/web/packages/design/src/Icon/Icons/AddUsers.tsx index 4daa30af03e9e..291d1107fd1e0 100644 --- a/web/packages/design/src/Icon/Icons/AddUsers.tsx +++ b/web/packages/design/src/Icon/Icons/AddUsers.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function AddUsers({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const AddUsers = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/AlarmRing.tsx b/web/packages/design/src/Icon/Icons/AlarmRing.tsx index 3a281b895a7a2..74c7d81373360 100644 --- a/web/packages/design/src/Icon/Icons/AlarmRing.tsx +++ b/web/packages/design/src/Icon/Icons/AlarmRing.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function AlarmRing({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const AlarmRing = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -65,5 +68,5 @@ export function AlarmRing({ size = 24, color, ...otherProps }: IconProps) { /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/AmazonAws.tsx b/web/packages/design/src/Icon/Icons/AmazonAws.tsx index 2a7d1c3d25c93..0b95de05e7b49 100644 --- a/web/packages/design/src/Icon/Icons/AmazonAws.tsx +++ b/web/packages/design/src/Icon/Icons/AmazonAws.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function AmazonAws({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const AmazonAws = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Apartment.tsx b/web/packages/design/src/Icon/Icons/Apartment.tsx index c7f94a1dd130f..dbacc005e643f 100644 --- a/web/packages/design/src/Icon/Icons/Apartment.tsx +++ b/web/packages/design/src/Icon/Icons/Apartment.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Apartment({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Apartment = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Apple.tsx b/web/packages/design/src/Icon/Icons/Apple.tsx index 1646852494d26..111db50141e78 100644 --- a/web/packages/design/src/Icon/Icons/Apple.tsx +++ b/web/packages/design/src/Icon/Icons/Apple.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Apple({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Apple = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Application.tsx b/web/packages/design/src/Icon/Icons/Application.tsx index 8463784e06942..d4170c24141d0 100644 --- a/web/packages/design/src/Icon/Icons/Application.tsx +++ b/web/packages/design/src/Icon/Icons/Application.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Application({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Application = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -64,5 +67,5 @@ export function Application({ size = 24, color, ...otherProps }: IconProps) { d="M2.25 5.25C2.25 4.42157 2.92157 3.75 3.75 3.75H20.25C21.0784 3.75 21.75 4.42157 21.75 5.25V18.75C21.75 19.5784 21.0784 20.25 20.25 20.25H3.75C2.92157 20.25 2.25 19.5784 2.25 18.75V5.25ZM20.25 5.25H3.75V18.75H20.25V5.25Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Archive.tsx b/web/packages/design/src/Icon/Icons/Archive.tsx index bb0d928fa815b..338a6a76d63b0 100644 --- a/web/packages/design/src/Icon/Icons/Archive.tsx +++ b/web/packages/design/src/Icon/Icons/Archive.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Archive({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Archive = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ArrowBack.tsx b/web/packages/design/src/Icon/Icons/ArrowBack.tsx index 0de01b394a7f4..3c083daee01fc 100644 --- a/web/packages/design/src/Icon/Icons/ArrowBack.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowBack.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,16 +50,17 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ArrowBack({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ArrowBack = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ArrowDown.tsx b/web/packages/design/src/Icon/Icons/ArrowDown.tsx index 907178413e8f6..185cdece515c9 100644 --- a/web/packages/design/src/Icon/Icons/ArrowDown.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowDown.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ArrowDown({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ArrowDown = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ArrowFatLinesUp.tsx b/web/packages/design/src/Icon/Icons/ArrowFatLinesUp.tsx index 90f912bc95c73..45caeec4808d1 100644 --- a/web/packages/design/src/Icon/Icons/ArrowFatLinesUp.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowFatLinesUp.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ArrowFatLinesUp({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const ArrowFatLinesUp = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ArrowForward.tsx b/web/packages/design/src/Icon/Icons/ArrowForward.tsx index 50c9157d56b48..0453d29fc5774 100644 --- a/web/packages/design/src/Icon/Icons/ArrowForward.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowForward.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ArrowForward({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ArrowForward = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ArrowLeft.tsx b/web/packages/design/src/Icon/Icons/ArrowLeft.tsx index a7496ae93920f..7546e06847736 100644 --- a/web/packages/design/src/Icon/Icons/ArrowLeft.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowLeft.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,16 +50,17 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ArrowLeft({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ArrowLeft = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx b/web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx index 8eceb19b95985..b37403bc1694d 100644 --- a/web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,16 +50,17 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ArrowLineLeft({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ArrowLineLeft = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ArrowRight.tsx b/web/packages/design/src/Icon/Icons/ArrowRight.tsx index 3ac8846d4a811..53f102b391611 100644 --- a/web/packages/design/src/Icon/Icons/ArrowRight.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowRight.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ArrowRight({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ArrowRight = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ArrowSquareOut.tsx b/web/packages/design/src/Icon/Icons/ArrowSquareOut.tsx index 309810dcea743..b8237a4b8d252 100644 --- a/web/packages/design/src/Icon/Icons/ArrowSquareOut.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowSquareOut.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ArrowSquareOut({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ArrowSquareOut = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ArrowUp.tsx b/web/packages/design/src/Icon/Icons/ArrowUp.tsx index b932ea5886975..348883c5c463d 100644 --- a/web/packages/design/src/Icon/Icons/ArrowUp.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowUp.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ArrowUp({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ArrowUp = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ArrowsIn.tsx b/web/packages/design/src/Icon/Icons/ArrowsIn.tsx index d5e54b7bbf996..e07e1dff510cb 100644 --- a/web/packages/design/src/Icon/Icons/ArrowsIn.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowsIn.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ArrowsIn({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ArrowsIn = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ArrowsOut.tsx b/web/packages/design/src/Icon/Icons/ArrowsOut.tsx index 215beffb58f77..1aad6fa766e9f 100644 --- a/web/packages/design/src/Icon/Icons/ArrowsOut.tsx +++ b/web/packages/design/src/Icon/Icons/ArrowsOut.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ArrowsOut({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ArrowsOut = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/BellRinging.tsx b/web/packages/design/src/Icon/Icons/BellRinging.tsx index 97cea1e9e506f..3a54175c3f896 100644 --- a/web/packages/design/src/Icon/Icons/BellRinging.tsx +++ b/web/packages/design/src/Icon/Icons/BellRinging.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function BellRinging({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const BellRinging = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/BookOpenText.tsx b/web/packages/design/src/Icon/Icons/BookOpenText.tsx index aa86e0a5d0f18..390229125f213 100644 --- a/web/packages/design/src/Icon/Icons/BookOpenText.tsx +++ b/web/packages/design/src/Icon/Icons/BookOpenText.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function BookOpenText({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const BookOpenText = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -65,5 +68,5 @@ export function BookOpenText({ size = 24, color, ...otherProps }: IconProps) { d="M1.93934 3.93934C2.22064 3.65804 2.60217 3.5 3 3.5H9C9.99456 3.5 10.9484 3.89509 11.6517 4.59835C11.7779 4.72456 11.8941 4.85884 12 4.99998C12.1059 4.85884 12.2221 4.72456 12.3484 4.59835C13.0516 3.89509 14.0054 3.5 15 3.5H21C21.3978 3.5 21.7794 3.65804 22.0607 3.93934C22.342 4.22065 22.5 4.60218 22.5 5V17C22.5 17.3978 22.342 17.7794 22.0607 18.0607C21.7794 18.342 21.3978 18.5 21 18.5H15C14.4033 18.5 13.831 18.7371 13.409 19.159C12.9871 19.581 12.75 20.1533 12.75 20.75C12.75 21.1642 12.4142 21.5 12 21.5C11.5858 21.5 11.25 21.1642 11.25 20.75C11.25 20.1533 11.0129 19.581 10.591 19.159C10.169 18.7371 9.59674 18.5 9 18.5H3C2.60218 18.5 2.22065 18.342 1.93934 18.0607C1.65803 17.7794 1.5 17.3978 1.5 17V5C1.5 4.60218 1.65804 4.22064 1.93934 3.93934ZM12.75 17.75V7.25C12.75 6.65326 12.9871 6.08097 13.409 5.65901C13.831 5.23705 14.4033 5 15 5L21 5V17H15C14.1839 17 13.3953 17.266 12.75 17.75ZM10.591 5.65901C11.0129 6.08097 11.25 6.65326 11.25 7.25V17.75C10.6047 17.266 9.81606 17 9 17H3V5L9 5C9.59674 5 10.169 5.23705 10.591 5.65901Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Bots.tsx b/web/packages/design/src/Icon/Icons/Bots.tsx index d58983844689b..130d566fef584 100644 --- a/web/packages/design/src/Icon/Icons/Bots.tsx +++ b/web/packages/design/src/Icon/Icons/Bots.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Bots({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Bots = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Broadcast.tsx b/web/packages/design/src/Icon/Icons/Broadcast.tsx index 160e232e2f3b2..903cee7d5cf57 100644 --- a/web/packages/design/src/Icon/Icons/Broadcast.tsx +++ b/web/packages/design/src/Icon/Icons/Broadcast.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Broadcast({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Broadcast = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -66,5 +69,5 @@ export function Broadcast({ size = 24, color, ...otherProps }: IconProps) { d="M8.25 12C8.25 9.92893 9.92893 8.25 12 8.25C14.0711 8.25 15.75 9.92893 15.75 12C15.75 14.0711 14.0711 15.75 12 15.75C9.92893 15.75 8.25 14.0711 8.25 12ZM12 9.75C10.7574 9.75 9.75 10.7574 9.75 12C9.75 13.2426 10.7574 14.25 12 14.25C13.2426 14.25 14.25 13.2426 14.25 12C14.25 10.7574 13.2426 9.75 12 9.75Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/BroadcastSlash.tsx b/web/packages/design/src/Icon/Icons/BroadcastSlash.tsx index 9daee15d4bced..ab8e6327b4c29 100644 --- a/web/packages/design/src/Icon/Icons/BroadcastSlash.tsx +++ b/web/packages/design/src/Icon/Icons/BroadcastSlash.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function BroadcastSlash({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const BroadcastSlash = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Bubble.tsx b/web/packages/design/src/Icon/Icons/Bubble.tsx index c94ada7cff62e..b14de9605dff2 100644 --- a/web/packages/design/src/Icon/Icons/Bubble.tsx +++ b/web/packages/design/src/Icon/Icons/Bubble.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Bubble({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Bubble = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CCAmex.tsx b/web/packages/design/src/Icon/Icons/CCAmex.tsx index 542d55fc92e10..4a37623407611 100644 --- a/web/packages/design/src/Icon/Icons/CCAmex.tsx +++ b/web/packages/design/src/Icon/Icons/CCAmex.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CCAmex({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CCAmex = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CCDiscover.tsx b/web/packages/design/src/Icon/Icons/CCDiscover.tsx index 7667482ecc24c..7598078b826e7 100644 --- a/web/packages/design/src/Icon/Icons/CCDiscover.tsx +++ b/web/packages/design/src/Icon/Icons/CCDiscover.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CCDiscover({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CCDiscover = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CCMasterCard.tsx b/web/packages/design/src/Icon/Icons/CCMasterCard.tsx index 436f1275d9755..ce752556192f8 100644 --- a/web/packages/design/src/Icon/Icons/CCMasterCard.tsx +++ b/web/packages/design/src/Icon/Icons/CCMasterCard.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CCMasterCard({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CCMasterCard = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CCStripe.tsx b/web/packages/design/src/Icon/Icons/CCStripe.tsx index b4ba6f3287ca1..98acf2536c0f8 100644 --- a/web/packages/design/src/Icon/Icons/CCStripe.tsx +++ b/web/packages/design/src/Icon/Icons/CCStripe.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CCStripe({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CCStripe = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CCVisa.tsx b/web/packages/design/src/Icon/Icons/CCVisa.tsx index 0d72543be8e63..bfd54fbfc1907 100644 --- a/web/packages/design/src/Icon/Icons/CCVisa.tsx +++ b/web/packages/design/src/Icon/Icons/CCVisa.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CCVisa({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CCVisa = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Calendar.tsx b/web/packages/design/src/Icon/Icons/Calendar.tsx index 224ee96feacf9..7d8140bd01275 100644 --- a/web/packages/design/src/Icon/Icons/Calendar.tsx +++ b/web/packages/design/src/Icon/Icons/Calendar.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Calendar({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Calendar = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -64,5 +67,5 @@ export function Calendar({ size = 24, color, ...otherProps }: IconProps) { d="M17.25 2.25C17.25 1.83579 16.9142 1.5 16.5 1.5C16.0858 1.5 15.75 1.83579 15.75 2.25V3H8.25V2.25C8.25 1.83579 7.91421 1.5 7.5 1.5C7.08579 1.5 6.75 1.83579 6.75 2.25V3H4.5C3.67157 3 3 3.67157 3 4.5V19.5C3 20.3284 3.67157 21 4.5 21H19.5C20.3284 21 21 20.3284 21 19.5V4.5C21 3.67157 20.3284 3 19.5 3H17.25V2.25ZM19.5 4.5H17.25V5.25C17.25 5.66421 16.9142 6 16.5 6C16.0858 6 15.75 5.66421 15.75 5.25V4.5H8.25V5.25C8.25 5.66421 7.91421 6 7.5 6C7.08579 6 6.75 5.66421 6.75 5.25V4.5H4.5V7.5H19.5V4.5ZM4.5 9H19.5V19.5H4.5V9Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Camera.tsx b/web/packages/design/src/Icon/Icons/Camera.tsx index a8d7d728bbb09..bb781f825356c 100644 --- a/web/packages/design/src/Icon/Icons/Camera.tsx +++ b/web/packages/design/src/Icon/Icons/Camera.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Camera({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Camera = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CardView.tsx b/web/packages/design/src/Icon/Icons/CardView.tsx index ee0dcc238e8c9..0683805ae8db6 100644 --- a/web/packages/design/src/Icon/Icons/CardView.tsx +++ b/web/packages/design/src/Icon/Icons/CardView.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CardView({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CardView = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Cash.tsx b/web/packages/design/src/Icon/Icons/Cash.tsx index 83dd3b199412c..bdca4615dffa3 100644 --- a/web/packages/design/src/Icon/Icons/Cash.tsx +++ b/web/packages/design/src/Icon/Icons/Cash.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Cash({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Cash = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Chart.tsx b/web/packages/design/src/Icon/Icons/Chart.tsx index 4baecea557d5a..222e7224e658d 100644 --- a/web/packages/design/src/Icon/Icons/Chart.tsx +++ b/web/packages/design/src/Icon/Icons/Chart.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Chart({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Chart = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ChatBubble.tsx b/web/packages/design/src/Icon/Icons/ChatBubble.tsx index ed809e95fc934..714f83e25421d 100644 --- a/web/packages/design/src/Icon/Icons/ChatBubble.tsx +++ b/web/packages/design/src/Icon/Icons/ChatBubble.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ChatBubble({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ChatBubble = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ChatCircleSparkle.tsx b/web/packages/design/src/Icon/Icons/ChatCircleSparkle.tsx index cd046fb281347..414ae583daaf1 100644 --- a/web/packages/design/src/Icon/Icons/ChatCircleSparkle.tsx +++ b/web/packages/design/src/Icon/Icons/ChatCircleSparkle.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ChatCircleSparkle({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const ChatCircleSparkle = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Check.tsx b/web/packages/design/src/Icon/Icons/Check.tsx index f1de7010e4f3f..e82758226213e 100644 --- a/web/packages/design/src/Icon/Icons/Check.tsx +++ b/web/packages/design/src/Icon/Icons/Check.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Check({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Check = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CheckThick.tsx b/web/packages/design/src/Icon/Icons/CheckThick.tsx index 486e3c7aa923f..96db8cf7b63b8 100644 --- a/web/packages/design/src/Icon/Icons/CheckThick.tsx +++ b/web/packages/design/src/Icon/Icons/CheckThick.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CheckThick({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CheckThick = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Checks.tsx b/web/packages/design/src/Icon/Icons/Checks.tsx index d954dc9280dbc..581b12db4226a 100644 --- a/web/packages/design/src/Icon/Icons/Checks.tsx +++ b/web/packages/design/src/Icon/Icons/Checks.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,16 +50,17 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Checks({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Checks = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ChevronCircleDown.tsx b/web/packages/design/src/Icon/Icons/ChevronCircleDown.tsx index 91a7c1f7a5b16..6379f8e59d941 100644 --- a/web/packages/design/src/Icon/Icons/ChevronCircleDown.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronCircleDown.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ChevronCircleDown({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const ChevronCircleDown = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ChevronCircleLeft.tsx b/web/packages/design/src/Icon/Icons/ChevronCircleLeft.tsx index 104a4df38804c..e996d8edd1d57 100644 --- a/web/packages/design/src/Icon/Icons/ChevronCircleLeft.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronCircleLeft.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ChevronCircleLeft({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const ChevronCircleLeft = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ChevronCircleRight.tsx b/web/packages/design/src/Icon/Icons/ChevronCircleRight.tsx index 47ccf26a49971..4c3a86f8f2d2f 100644 --- a/web/packages/design/src/Icon/Icons/ChevronCircleRight.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronCircleRight.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ChevronCircleRight({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const ChevronCircleRight = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ChevronCircleUp.tsx b/web/packages/design/src/Icon/Icons/ChevronCircleUp.tsx index f985f5e66c6a0..dc6367605f1d0 100644 --- a/web/packages/design/src/Icon/Icons/ChevronCircleUp.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronCircleUp.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ChevronCircleUp({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const ChevronCircleUp = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ChevronDown.tsx b/web/packages/design/src/Icon/Icons/ChevronDown.tsx index c5af174bfec8e..907e78d1d6470 100644 --- a/web/packages/design/src/Icon/Icons/ChevronDown.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronDown.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ChevronDown({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ChevronDown = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ChevronLeft.tsx b/web/packages/design/src/Icon/Icons/ChevronLeft.tsx index 85b71f40eaadf..f50f5ff0faae5 100644 --- a/web/packages/design/src/Icon/Icons/ChevronLeft.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronLeft.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ChevronLeft({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ChevronLeft = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ChevronRight.tsx b/web/packages/design/src/Icon/Icons/ChevronRight.tsx index 7f4a6d0191a2d..fb1f999536beb 100644 --- a/web/packages/design/src/Icon/Icons/ChevronRight.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronRight.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ChevronRight({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ChevronRight = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ChevronUp.tsx b/web/packages/design/src/Icon/Icons/ChevronUp.tsx index c5fd2eb30803d..b68a8f49b9a99 100644 --- a/web/packages/design/src/Icon/Icons/ChevronUp.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronUp.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ChevronUp({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ChevronUp = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ChevronsVertical.tsx b/web/packages/design/src/Icon/Icons/ChevronsVertical.tsx index 347ff0b8b9c08..af8c404deb649 100644 --- a/web/packages/design/src/Icon/Icons/ChevronsVertical.tsx +++ b/web/packages/design/src/Icon/Icons/ChevronsVertical.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,20 +50,17 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ChevronsVertical({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const ChevronsVertical = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CircleArrowLeft.tsx b/web/packages/design/src/Icon/Icons/CircleArrowLeft.tsx index 8012d3142b1a9..061067863c128 100644 --- a/web/packages/design/src/Icon/Icons/CircleArrowLeft.tsx +++ b/web/packages/design/src/Icon/Icons/CircleArrowLeft.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CircleArrowLeft({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const CircleArrowLeft = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CircleArrowRight.tsx b/web/packages/design/src/Icon/Icons/CircleArrowRight.tsx index 7001c924817cb..2a825ccd6a7eb 100644 --- a/web/packages/design/src/Icon/Icons/CircleArrowRight.tsx +++ b/web/packages/design/src/Icon/Icons/CircleArrowRight.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CircleArrowRight({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const CircleArrowRight = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CircleCheck.tsx b/web/packages/design/src/Icon/Icons/CircleCheck.tsx index 39db44d733a33..7225ede5a5cc3 100644 --- a/web/packages/design/src/Icon/Icons/CircleCheck.tsx +++ b/web/packages/design/src/Icon/Icons/CircleCheck.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CircleCheck({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CircleCheck = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CircleCross.tsx b/web/packages/design/src/Icon/Icons/CircleCross.tsx index aa3a24cb51552..0e3a30943ae79 100644 --- a/web/packages/design/src/Icon/Icons/CircleCross.tsx +++ b/web/packages/design/src/Icon/Icons/CircleCross.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CircleCross({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CircleCross = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CirclePause.tsx b/web/packages/design/src/Icon/Icons/CirclePause.tsx index 3150774b82681..fbec5ef543a86 100644 --- a/web/packages/design/src/Icon/Icons/CirclePause.tsx +++ b/web/packages/design/src/Icon/Icons/CirclePause.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CirclePause({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CirclePause = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -64,5 +67,5 @@ export function CirclePause({ size = 24, color, ...otherProps }: IconProps) { d="M2.25 12C2.25 6.61522 6.61522 2.25 12 2.25C17.3848 2.25 21.75 6.61522 21.75 12C21.75 17.3848 17.3848 21.75 12 21.75C6.61522 21.75 2.25 17.3848 2.25 12ZM12 3.75C7.44365 3.75 3.75 7.44365 3.75 12C3.75 16.5563 7.44365 20.25 12 20.25C16.5563 20.25 20.25 16.5563 20.25 12C20.25 7.44365 16.5563 3.75 12 3.75Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CirclePlay.tsx b/web/packages/design/src/Icon/Icons/CirclePlay.tsx index c28d3546b650d..869fe0ef99ff4 100644 --- a/web/packages/design/src/Icon/Icons/CirclePlay.tsx +++ b/web/packages/design/src/Icon/Icons/CirclePlay.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CirclePlay({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CirclePlay = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CircleStop.tsx b/web/packages/design/src/Icon/Icons/CircleStop.tsx index efaaa00e62d01..48d7ef006875f 100644 --- a/web/packages/design/src/Icon/Icons/CircleStop.tsx +++ b/web/packages/design/src/Icon/Icons/CircleStop.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CircleStop({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CircleStop = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Cli.tsx b/web/packages/design/src/Icon/Icons/Cli.tsx index 7222ba641585e..84a6243942be5 100644 --- a/web/packages/design/src/Icon/Icons/Cli.tsx +++ b/web/packages/design/src/Icon/Icons/Cli.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Cli({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Cli = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Clipboard.tsx b/web/packages/design/src/Icon/Icons/Clipboard.tsx index b15961ca1cba0..a5f2bbf7ed6f2 100644 --- a/web/packages/design/src/Icon/Icons/Clipboard.tsx +++ b/web/packages/design/src/Icon/Icons/Clipboard.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Clipboard({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Clipboard = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -64,5 +67,5 @@ export function Clipboard({ size = 24, color, ...otherProps }: IconProps) { d="M8.64589 3C8.70142 2.93792 8.75881 2.87723 8.81802 2.81802C9.66193 1.97411 10.8065 1.5 12 1.5C13.1935 1.5 14.3381 1.97411 15.182 2.81802C15.2412 2.87723 15.2986 2.93792 15.3541 3H18.75C19.1478 3 19.5294 3.15804 19.8107 3.43934C20.092 3.72065 20.25 4.10218 20.25 4.5V20.25C20.25 20.6478 20.092 21.0294 19.8107 21.3107C19.5294 21.592 19.1478 21.75 18.75 21.75H5.25C4.85218 21.75 4.47065 21.592 4.18934 21.3107C3.90804 21.0294 3.75 20.6478 3.75 20.25V4.5C3.75 4.10217 3.90804 3.72064 4.18934 3.43934C4.47064 3.15804 4.85217 3 5.25 3H8.64589ZM9.87868 3.87868C10.4413 3.31607 11.2044 3 12 3C12.7956 3 13.5587 3.31607 14.1213 3.87868C14.2202 3.97758 14.3115 4.08267 14.3948 4.19305C14.398 4.19746 14.4013 4.20184 14.4046 4.20617C14.7889 4.72126 15 5.34975 15 6H9C9 5.34975 9.21111 4.72126 9.59537 4.20617C9.5987 4.20183 9.60198 4.19746 9.60521 4.19305C9.6885 4.08268 9.77978 3.97758 9.87868 3.87868ZM7.75736 4.5H5.25L5.25 20.25H18.75L18.75 4.5H16.2426C16.4114 4.97731 16.5 5.48409 16.5 6V6.75C16.5 7.16421 16.1642 7.5 15.75 7.5H8.25C7.83579 7.5 7.5 7.16421 7.5 6.75V6C7.5 5.48409 7.58859 4.97731 7.75736 4.5Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ClipboardUser.tsx b/web/packages/design/src/Icon/Icons/ClipboardUser.tsx index 367ab412e6c65..8568eed3fbac7 100644 --- a/web/packages/design/src/Icon/Icons/ClipboardUser.tsx +++ b/web/packages/design/src/Icon/Icons/ClipboardUser.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ClipboardUser({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ClipboardUser = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Clock.tsx b/web/packages/design/src/Icon/Icons/Clock.tsx index edcea59a432c7..b1e5f61c42b0a 100644 --- a/web/packages/design/src/Icon/Icons/Clock.tsx +++ b/web/packages/design/src/Icon/Icons/Clock.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Clock({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Clock = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Cloud.tsx b/web/packages/design/src/Icon/Icons/Cloud.tsx index a9a6177628e8e..15bada7610b11 100644 --- a/web/packages/design/src/Icon/Icons/Cloud.tsx +++ b/web/packages/design/src/Icon/Icons/Cloud.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Cloud({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Cloud = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Cluster.tsx b/web/packages/design/src/Icon/Icons/Cluster.tsx index 0401a992421c8..f9cecdf71b635 100644 --- a/web/packages/design/src/Icon/Icons/Cluster.tsx +++ b/web/packages/design/src/Icon/Icons/Cluster.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Cluster({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Cluster = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Code.tsx b/web/packages/design/src/Icon/Icons/Code.tsx index 60d05a8959e99..dc843fa40cd41 100644 --- a/web/packages/design/src/Icon/Icons/Code.tsx +++ b/web/packages/design/src/Icon/Icons/Code.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Code({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Code = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Cog.tsx b/web/packages/design/src/Icon/Icons/Cog.tsx index 9f1d1d4b2e5cd..78e3733610bb5 100644 --- a/web/packages/design/src/Icon/Icons/Cog.tsx +++ b/web/packages/design/src/Icon/Icons/Cog.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Cog({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Cog = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Config.tsx b/web/packages/design/src/Icon/Icons/Config.tsx index aaa2de7747b90..d58b54ab9af50 100644 --- a/web/packages/design/src/Icon/Icons/Config.tsx +++ b/web/packages/design/src/Icon/Icons/Config.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Config({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Config = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -68,5 +71,5 @@ export function Config({ size = 24, color, ...otherProps }: IconProps) { d="M2.25 3.9C2.25 3.12257 2.79934 2.25 3.75 2.25H20.25C21.2007 2.25 21.75 3.12257 21.75 3.9V20.1C21.75 20.8774 21.2007 21.75 20.25 21.75H3.75C2.79934 21.75 2.25 20.8774 2.25 20.1V3.9ZM3.79101 3.75C3.77346 3.77438 3.75 3.82494 3.75 3.9V20.1C3.75 20.1751 3.77346 20.2256 3.79101 20.25H20.209C20.2265 20.2256 20.25 20.1751 20.25 20.1V3.9C20.25 3.82494 20.2265 3.77438 20.209 3.75H3.79101Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Contract.tsx b/web/packages/design/src/Icon/Icons/Contract.tsx index 7a900edf4bf0b..e2c4263e20c32 100644 --- a/web/packages/design/src/Icon/Icons/Contract.tsx +++ b/web/packages/design/src/Icon/Icons/Contract.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,18 +50,19 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Contract({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Contract = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Copy.tsx b/web/packages/design/src/Icon/Icons/Copy.tsx index a27280c09fb7c..5647e8e260c53 100644 --- a/web/packages/design/src/Icon/Icons/Copy.tsx +++ b/web/packages/design/src/Icon/Icons/Copy.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Copy({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Copy = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/CreditCard.tsx b/web/packages/design/src/Icon/Icons/CreditCard.tsx index 5911ddc3140d0..feab4fbd36da2 100644 --- a/web/packages/design/src/Icon/Icons/CreditCard.tsx +++ b/web/packages/design/src/Icon/Icons/CreditCard.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function CreditCard({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const CreditCard = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Cross.tsx b/web/packages/design/src/Icon/Icons/Cross.tsx index dc9545e4e1bb0..a7d33893527b4 100644 --- a/web/packages/design/src/Icon/Icons/Cross.tsx +++ b/web/packages/design/src/Icon/Icons/Cross.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,10 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Cross({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Cross = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Crown.tsx b/web/packages/design/src/Icon/Icons/Crown.tsx index 4d6ccb97d9d43..a77187a9e3dbd 100644 --- a/web/packages/design/src/Icon/Icons/Crown.tsx +++ b/web/packages/design/src/Icon/Icons/Crown.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,19 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Crown({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Crown = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Database.tsx b/web/packages/design/src/Icon/Icons/Database.tsx index 7c0b1a2b14be6..793293815064b 100644 --- a/web/packages/design/src/Icon/Icons/Database.tsx +++ b/web/packages/design/src/Icon/Icons/Database.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Database({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Database = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Desktop.tsx b/web/packages/design/src/Icon/Icons/Desktop.tsx index 5ecdbec38b5a2..7e15189e5d7eb 100644 --- a/web/packages/design/src/Icon/Icons/Desktop.tsx +++ b/web/packages/design/src/Icon/Icons/Desktop.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Desktop({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Desktop = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/DeviceMobileCamera.tsx b/web/packages/design/src/Icon/Icons/DeviceMobileCamera.tsx index a1a0b628aaec1..a518902d4b9a2 100644 --- a/web/packages/design/src/Icon/Icons/DeviceMobileCamera.tsx +++ b/web/packages/design/src/Icon/Icons/DeviceMobileCamera.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function DeviceMobileCamera({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const DeviceMobileCamera = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Devices.tsx b/web/packages/design/src/Icon/Icons/Devices.tsx index dd4f6037b9fc0..8ce202e259c01 100644 --- a/web/packages/design/src/Icon/Icons/Devices.tsx +++ b/web/packages/design/src/Icon/Icons/Devices.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Devices({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Devices = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Download.tsx b/web/packages/design/src/Icon/Icons/Download.tsx index 388b84d2fbed9..c75abbeda14f5 100644 --- a/web/packages/design/src/Icon/Icons/Download.tsx +++ b/web/packages/design/src/Icon/Icons/Download.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,16 +50,17 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Download({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Download = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Earth.tsx b/web/packages/design/src/Icon/Icons/Earth.tsx index 65bb7e21a4d2a..9d56b8350f9c4 100644 --- a/web/packages/design/src/Icon/Icons/Earth.tsx +++ b/web/packages/design/src/Icon/Icons/Earth.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Earth({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Earth = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Edit.tsx b/web/packages/design/src/Icon/Icons/Edit.tsx index 57b0303d2c1ab..2d832f7e6021a 100644 --- a/web/packages/design/src/Icon/Icons/Edit.tsx +++ b/web/packages/design/src/Icon/Icons/Edit.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Edit({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Edit = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Ellipsis.tsx b/web/packages/design/src/Icon/Icons/Ellipsis.tsx index fc4c96918955f..22ef42e8fc652 100644 --- a/web/packages/design/src/Icon/Icons/Ellipsis.tsx +++ b/web/packages/design/src/Icon/Icons/Ellipsis.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Ellipsis({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Ellipsis = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/EmailSolid.tsx b/web/packages/design/src/Icon/Icons/EmailSolid.tsx index 5360af236538e..00c4e28260939 100644 --- a/web/packages/design/src/Icon/Icons/EmailSolid.tsx +++ b/web/packages/design/src/Icon/Icons/EmailSolid.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function EmailSolid({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const EmailSolid = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/EnvelopeOpen.tsx b/web/packages/design/src/Icon/Icons/EnvelopeOpen.tsx index 0b9793cff885f..d6897f6ab90f8 100644 --- a/web/packages/design/src/Icon/Icons/EnvelopeOpen.tsx +++ b/web/packages/design/src/Icon/Icons/EnvelopeOpen.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function EnvelopeOpen({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const EnvelopeOpen = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/EqualizersVertical.tsx b/web/packages/design/src/Icon/Icons/EqualizersVertical.tsx index 540da0de4a764..0885164e6ffa7 100644 --- a/web/packages/design/src/Icon/Icons/EqualizersVertical.tsx +++ b/web/packages/design/src/Icon/Icons/EqualizersVertical.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function EqualizersVertical({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const EqualizersVertical = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -67,5 +66,5 @@ export function EqualizersVertical({ - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Expand.tsx b/web/packages/design/src/Icon/Icons/Expand.tsx index 780b626486e1f..e1e9bc86467b0 100644 --- a/web/packages/design/src/Icon/Icons/Expand.tsx +++ b/web/packages/design/src/Icon/Icons/Expand.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,18 +50,19 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Expand({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Expand = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Facebook.tsx b/web/packages/design/src/Icon/Icons/Facebook.tsx index 68321b7d1c714..f2b33bfac090a 100644 --- a/web/packages/design/src/Icon/Icons/Facebook.tsx +++ b/web/packages/design/src/Icon/Icons/Facebook.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Facebook({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Facebook = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/FingerprintSimple.tsx b/web/packages/design/src/Icon/Icons/FingerprintSimple.tsx index b26fc3eae42c3..1740858bc27bc 100644 --- a/web/packages/design/src/Icon/Icons/FingerprintSimple.tsx +++ b/web/packages/design/src/Icon/Icons/FingerprintSimple.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function FingerprintSimple({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const FingerprintSimple = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Floppy.tsx b/web/packages/design/src/Icon/Icons/Floppy.tsx index 3d4e535e94360..b128d539d0eeb 100644 --- a/web/packages/design/src/Icon/Icons/Floppy.tsx +++ b/web/packages/design/src/Icon/Icons/Floppy.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,18 +50,19 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Floppy({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Floppy = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/FlowArrow.tsx b/web/packages/design/src/Icon/Icons/FlowArrow.tsx index 28b67f46ead22..67c80917c0037 100644 --- a/web/packages/design/src/Icon/Icons/FlowArrow.tsx +++ b/web/packages/design/src/Icon/Icons/FlowArrow.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function FlowArrow({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const FlowArrow = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/FolderPlus.tsx b/web/packages/design/src/Icon/Icons/FolderPlus.tsx index 7aaa6cc4e0ae4..7560cfdbcc767 100644 --- a/web/packages/design/src/Icon/Icons/FolderPlus.tsx +++ b/web/packages/design/src/Icon/Icons/FolderPlus.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function FolderPlus({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const FolderPlus = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/FolderShared.tsx b/web/packages/design/src/Icon/Icons/FolderShared.tsx index bebbf3a5ea0e4..fa68d3bf9a6cc 100644 --- a/web/packages/design/src/Icon/Icons/FolderShared.tsx +++ b/web/packages/design/src/Icon/Icons/FolderShared.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function FolderShared({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const FolderShared = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/GitHub.tsx b/web/packages/design/src/Icon/Icons/GitHub.tsx index a691b48c92190..e319923a82f22 100644 --- a/web/packages/design/src/Icon/Icons/GitHub.tsx +++ b/web/packages/design/src/Icon/Icons/GitHub.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function GitHub({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const GitHub = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Google.tsx b/web/packages/design/src/Icon/Icons/Google.tsx index 9cde7f9a26e8c..0c6f4b7cee93b 100644 --- a/web/packages/design/src/Icon/Icons/Google.tsx +++ b/web/packages/design/src/Icon/Icons/Google.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Google({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Google = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Graph.tsx b/web/packages/design/src/Icon/Icons/Graph.tsx index 18cd6b75b8193..abdbf3e0b02c7 100644 --- a/web/packages/design/src/Icon/Icons/Graph.tsx +++ b/web/packages/design/src/Icon/Icons/Graph.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,10 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Graph({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Graph = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Hashtag.tsx b/web/packages/design/src/Icon/Icons/Hashtag.tsx index 51edfdc8c9790..b27a05bdb76b4 100644 --- a/web/packages/design/src/Icon/Icons/Hashtag.tsx +++ b/web/packages/design/src/Icon/Icons/Hashtag.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Hashtag({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Hashtag = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Headset.tsx b/web/packages/design/src/Icon/Icons/Headset.tsx index 8441b61f5d17b..77bfc140bb755 100644 --- a/web/packages/design/src/Icon/Icons/Headset.tsx +++ b/web/packages/design/src/Icon/Icons/Headset.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Headset({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Headset = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Home.tsx b/web/packages/design/src/Icon/Icons/Home.tsx index 6bc2ad3fac9cc..56c30696ade72 100644 --- a/web/packages/design/src/Icon/Icons/Home.tsx +++ b/web/packages/design/src/Icon/Icons/Home.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Home({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Home = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Info.tsx b/web/packages/design/src/Icon/Icons/Info.tsx index 2c2a05b0c084c..94502c5afef96 100644 --- a/web/packages/design/src/Icon/Icons/Info.tsx +++ b/web/packages/design/src/Icon/Icons/Info.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Info({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Info = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Integrations.tsx b/web/packages/design/src/Icon/Icons/Integrations.tsx index 336e8c08db40f..97b6e93061d28 100644 --- a/web/packages/design/src/Icon/Icons/Integrations.tsx +++ b/web/packages/design/src/Icon/Icons/Integrations.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Integrations({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Integrations = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Invoices.tsx b/web/packages/design/src/Icon/Icons/Invoices.tsx index c655cd3a02999..155dde536a928 100644 --- a/web/packages/design/src/Icon/Icons/Invoices.tsx +++ b/web/packages/design/src/Icon/Icons/Invoices.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Invoices({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Invoices = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -64,5 +67,5 @@ export function Invoices({ size = 24, color, ...otherProps }: IconProps) { d="M3.75 3.75C3.35217 3.75 2.97064 3.90804 2.68934 4.18934C2.40804 4.47064 2.25 4.85217 2.25 5.25V19.5C2.25 19.7599 2.38459 20.0013 2.6057 20.138C2.82681 20.2746 3.10292 20.2871 3.33541 20.1708L6 18.8385L8.66459 20.1708C8.87574 20.2764 9.12426 20.2764 9.33541 20.1708L12 18.8385L14.6646 20.1708C14.8757 20.2764 15.1243 20.2764 15.3354 20.1708L18 18.8385L20.6646 20.1708C20.8971 20.2871 21.1732 20.2746 21.3943 20.138C21.6154 20.0013 21.75 19.7599 21.75 19.5V5.25C21.75 4.85218 21.592 4.47065 21.3107 4.18934C21.0294 3.90804 20.6478 3.75 20.25 3.75H3.75ZM3.75 5.25L20.25 5.25V18.2865L18.3354 17.3292C18.1243 17.2236 17.8757 17.2236 17.6646 17.3292L15 18.6615L12.3354 17.3292C12.1243 17.2236 11.8757 17.2236 11.6646 17.3292L9 18.6615L6.33541 17.3292C6.12426 17.2236 5.87574 17.2236 5.66459 17.3292L3.75 18.2865L3.75 5.25Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Key.tsx b/web/packages/design/src/Icon/Icons/Key.tsx index 914dec7b3ffb1..06594fdb5cd41 100644 --- a/web/packages/design/src/Icon/Icons/Key.tsx +++ b/web/packages/design/src/Icon/Icons/Key.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Key({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Key = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/KeyHole.tsx b/web/packages/design/src/Icon/Icons/KeyHole.tsx index d4de448b1c294..ad645ee5bd63e 100644 --- a/web/packages/design/src/Icon/Icons/KeyHole.tsx +++ b/web/packages/design/src/Icon/Icons/KeyHole.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function KeyHole({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const KeyHole = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Keyboard.tsx b/web/packages/design/src/Icon/Icons/Keyboard.tsx index 83d4f855aa940..a82b738caf021 100644 --- a/web/packages/design/src/Icon/Icons/Keyboard.tsx +++ b/web/packages/design/src/Icon/Icons/Keyboard.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Keyboard({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Keyboard = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -67,5 +70,5 @@ export function Keyboard({ size = 24, color, ...otherProps }: IconProps) { d="M3.04594 4.5C2.19214 4.5 1.5 5.19214 1.5 6.04594V17.9541C1.5 18.8079 2.19214 19.5 3.04594 19.5H20.9541C21.8079 19.5 22.5 18.8079 22.5 17.9541V6.04594C22.5 5.19214 21.8079 4.5 20.9541 4.5H3.04594ZM3 6.04594C3 6.02057 3.02057 6 3.04594 6H20.9541C20.9794 6 21 6.02057 21 6.04594V17.9541C21 17.9794 20.9794 18 20.9541 18H3.04594C3.02057 18 3 17.9794 3 17.9541V6.04594Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Keypair.tsx b/web/packages/design/src/Icon/Icons/Keypair.tsx index 03b6b47d21c0d..d332e546d9c54 100644 --- a/web/packages/design/src/Icon/Icons/Keypair.tsx +++ b/web/packages/design/src/Icon/Icons/Keypair.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Keypair({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Keypair = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Kubernetes.tsx b/web/packages/design/src/Icon/Icons/Kubernetes.tsx index 5d437e3c335e8..e9fea277e12cd 100644 --- a/web/packages/design/src/Icon/Icons/Kubernetes.tsx +++ b/web/packages/design/src/Icon/Icons/Kubernetes.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Kubernetes({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Kubernetes = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Label.tsx b/web/packages/design/src/Icon/Icons/Label.tsx index 30d15b93a18a4..bf9b18ecab4a3 100644 --- a/web/packages/design/src/Icon/Icons/Label.tsx +++ b/web/packages/design/src/Icon/Icons/Label.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Label({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Label = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Lan.tsx b/web/packages/design/src/Icon/Icons/Lan.tsx index b096de016591c..04f2dc8465353 100644 --- a/web/packages/design/src/Icon/Icons/Lan.tsx +++ b/web/packages/design/src/Icon/Icons/Lan.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Lan({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Lan = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Laptop.tsx b/web/packages/design/src/Icon/Icons/Laptop.tsx index 19677af6c9213..6fbdc4360e141 100644 --- a/web/packages/design/src/Icon/Icons/Laptop.tsx +++ b/web/packages/design/src/Icon/Icons/Laptop.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Laptop({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Laptop = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Layout.tsx b/web/packages/design/src/Icon/Icons/Layout.tsx index d5c1852b80a21..a9ed1825bd0fb 100644 --- a/web/packages/design/src/Icon/Icons/Layout.tsx +++ b/web/packages/design/src/Icon/Icons/Layout.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,18 +50,19 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Layout({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Layout = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/License.tsx b/web/packages/design/src/Icon/Icons/License.tsx index 5e97fa4095a64..ccdac10d91f37 100644 --- a/web/packages/design/src/Icon/Icons/License.tsx +++ b/web/packages/design/src/Icon/Icons/License.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function License({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const License = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/LineSegment.tsx b/web/packages/design/src/Icon/Icons/LineSegment.tsx index d3bf34cdfd31c..a181febb58075 100644 --- a/web/packages/design/src/Icon/Icons/LineSegment.tsx +++ b/web/packages/design/src/Icon/Icons/LineSegment.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,18 +50,19 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function LineSegment({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const LineSegment = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/LineSegments.tsx b/web/packages/design/src/Icon/Icons/LineSegments.tsx index 4651cfa5974f8..4b1b2e4661853 100644 --- a/web/packages/design/src/Icon/Icons/LineSegments.tsx +++ b/web/packages/design/src/Icon/Icons/LineSegments.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,18 +50,19 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function LineSegments({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const LineSegments = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Link.tsx b/web/packages/design/src/Icon/Icons/Link.tsx index de4c3ff1e6458..d20f71ab92765 100644 --- a/web/packages/design/src/Icon/Icons/Link.tsx +++ b/web/packages/design/src/Icon/Icons/Link.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,11 +50,17 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Link({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Link = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Linkedin.tsx b/web/packages/design/src/Icon/Icons/Linkedin.tsx index ca886d25738f0..2ce67c7542812 100644 --- a/web/packages/design/src/Icon/Icons/Linkedin.tsx +++ b/web/packages/design/src/Icon/Icons/Linkedin.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Linkedin({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Linkedin = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -65,5 +68,5 @@ export function Linkedin({ size = 24, color, ...otherProps }: IconProps) { d="M2.25 3.75C2.25 2.92157 2.92157 2.25 3.75 2.25H20.25C21.0784 2.25 21.75 2.92157 21.75 3.75V20.25C21.75 21.0784 21.0784 21.75 20.25 21.75H3.75C2.92157 21.75 2.25 21.0784 2.25 20.25V3.75ZM20.25 3.75H3.75V20.25H20.25V3.75Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Linux.tsx b/web/packages/design/src/Icon/Icons/Linux.tsx index 6ba83d5480907..a6a392b11e7ac 100644 --- a/web/packages/design/src/Icon/Icons/Linux.tsx +++ b/web/packages/design/src/Icon/Icons/Linux.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Linux({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Linux = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ListAddCheck.tsx b/web/packages/design/src/Icon/Icons/ListAddCheck.tsx index 4434b2c203909..ff6051a33c432 100644 --- a/web/packages/design/src/Icon/Icons/ListAddCheck.tsx +++ b/web/packages/design/src/Icon/Icons/ListAddCheck.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ListAddCheck({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ListAddCheck = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -63,5 +66,5 @@ export function ListAddCheck({ size = 24, color, ...otherProps }: IconProps) { - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ListMagnifyingGlass.tsx b/web/packages/design/src/Icon/Icons/ListMagnifyingGlass.tsx index 4442a2fef2634..fb877475f6257 100644 --- a/web/packages/design/src/Icon/Icons/ListMagnifyingGlass.tsx +++ b/web/packages/design/src/Icon/Icons/ListMagnifyingGlass.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ListMagnifyingGlass({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const ListMagnifyingGlass = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -69,5 +68,5 @@ export function ListMagnifyingGlass({ d="M13.5 13.5C13.5 11.4289 15.1789 9.75 17.25 9.75C19.3211 9.75 21 11.4289 21 13.5C21 14.2642 20.7714 14.975 20.3789 15.5677L22.2803 17.4692C22.5732 17.7621 22.5732 18.237 22.2803 18.5299C21.9875 18.8228 21.5126 18.8228 21.2197 18.5299L19.3183 16.6285C18.7255 17.0213 18.0144 17.25 17.25 17.25C15.1789 17.25 13.5 15.5711 13.5 13.5ZM17.25 11.25C16.0074 11.25 15 12.2574 15 13.5C15 14.7426 16.0074 15.75 17.25 15.75C18.4926 15.75 19.5 14.7426 19.5 13.5C19.5 12.2574 18.4926 11.25 17.25 11.25Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ListThin.tsx b/web/packages/design/src/Icon/Icons/ListThin.tsx index b36f4f0d3cf9d..c6e8f676120f8 100644 --- a/web/packages/design/src/Icon/Icons/ListThin.tsx +++ b/web/packages/design/src/Icon/Icons/ListThin.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ListThin({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ListThin = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -63,5 +66,5 @@ export function ListThin({ size = 24, color, ...otherProps }: IconProps) { - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ListView.tsx b/web/packages/design/src/Icon/Icons/ListView.tsx index 89c936890a154..4187301593382 100644 --- a/web/packages/design/src/Icon/Icons/ListView.tsx +++ b/web/packages/design/src/Icon/Icons/ListView.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ListView({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ListView = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -63,5 +66,5 @@ export function ListView({ size = 24, color, ...otherProps }: IconProps) { - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Lock.tsx b/web/packages/design/src/Icon/Icons/Lock.tsx index 3923fc952f4ce..b03e85dcbda03 100644 --- a/web/packages/design/src/Icon/Icons/Lock.tsx +++ b/web/packages/design/src/Icon/Icons/Lock.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Lock({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Lock = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/LockKey.tsx b/web/packages/design/src/Icon/Icons/LockKey.tsx index a3efdab311c60..65b4043c5bcf5 100644 --- a/web/packages/design/src/Icon/Icons/LockKey.tsx +++ b/web/packages/design/src/Icon/Icons/LockKey.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function LockKey({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const LockKey = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Logout.tsx b/web/packages/design/src/Icon/Icons/Logout.tsx index fdffb67bf2e7d..fac3041b344a7 100644 --- a/web/packages/design/src/Icon/Icons/Logout.tsx +++ b/web/packages/design/src/Icon/Icons/Logout.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,16 +50,17 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Logout({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Logout = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Magnifier.tsx b/web/packages/design/src/Icon/Icons/Magnifier.tsx index f6cf426ed470e..79655e11a879e 100644 --- a/web/packages/design/src/Icon/Icons/Magnifier.tsx +++ b/web/packages/design/src/Icon/Icons/Magnifier.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Magnifier({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Magnifier = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/MagnifyingMinus.tsx b/web/packages/design/src/Icon/Icons/MagnifyingMinus.tsx index 5919e5d2ea3be..067bd5da684aa 100644 --- a/web/packages/design/src/Icon/Icons/MagnifyingMinus.tsx +++ b/web/packages/design/src/Icon/Icons/MagnifyingMinus.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,22 +50,19 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function MagnifyingMinus({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const MagnifyingMinus = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/MagnifyingPlus.tsx b/web/packages/design/src/Icon/Icons/MagnifyingPlus.tsx index 802f6bd222e98..f55daafef1397 100644 --- a/web/packages/design/src/Icon/Icons/MagnifyingPlus.tsx +++ b/web/packages/design/src/Icon/Icons/MagnifyingPlus.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,18 +50,19 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function MagnifyingPlus({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const MagnifyingPlus = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Memory.tsx b/web/packages/design/src/Icon/Icons/Memory.tsx index 2ada215529af7..8eb153a6ae295 100644 --- a/web/packages/design/src/Icon/Icons/Memory.tsx +++ b/web/packages/design/src/Icon/Icons/Memory.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Memory({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Memory = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Minus.tsx b/web/packages/design/src/Icon/Icons/Minus.tsx index e8b0afa4e6e72..97727d7854100 100644 --- a/web/packages/design/src/Icon/Icons/Minus.tsx +++ b/web/packages/design/src/Icon/Icons/Minus.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Minus({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Minus = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/MinusCircle.tsx b/web/packages/design/src/Icon/Icons/MinusCircle.tsx index fe3a181ffd912..11b3d787bae04 100644 --- a/web/packages/design/src/Icon/Icons/MinusCircle.tsx +++ b/web/packages/design/src/Icon/Icons/MinusCircle.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function MinusCircle({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const MinusCircle = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Moon.tsx b/web/packages/design/src/Icon/Icons/Moon.tsx index 06b5340f80bd6..f6e169b4ae512 100644 --- a/web/packages/design/src/Icon/Icons/Moon.tsx +++ b/web/packages/design/src/Icon/Icons/Moon.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Moon({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Moon = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/MoreHoriz.tsx b/web/packages/design/src/Icon/Icons/MoreHoriz.tsx index e9695c13d7548..0bf7676adae6b 100644 --- a/web/packages/design/src/Icon/Icons/MoreHoriz.tsx +++ b/web/packages/design/src/Icon/Icons/MoreHoriz.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,18 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function MoreHoriz({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const MoreHoriz = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/MoreVert.tsx b/web/packages/design/src/Icon/Icons/MoreVert.tsx index 7803bf267c4d1..8a3856fcc7dbe 100644 --- a/web/packages/design/src/Icon/Icons/MoreVert.tsx +++ b/web/packages/design/src/Icon/Icons/MoreVert.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,18 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function MoreVert({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const MoreVert = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Mute.tsx b/web/packages/design/src/Icon/Icons/Mute.tsx index f7f29176f5306..ee416e148204c 100644 --- a/web/packages/design/src/Icon/Icons/Mute.tsx +++ b/web/packages/design/src/Icon/Icons/Mute.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Mute({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Mute = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/NewTab.tsx b/web/packages/design/src/Icon/Icons/NewTab.tsx index dc54aca3184b8..6f45612a8f486 100644 --- a/web/packages/design/src/Icon/Icons/NewTab.tsx +++ b/web/packages/design/src/Icon/Icons/NewTab.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function NewTab({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const NewTab = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/NoteAdded.tsx b/web/packages/design/src/Icon/Icons/NoteAdded.tsx index 615c027a59d99..b0d27da43eb6b 100644 --- a/web/packages/design/src/Icon/Icons/NoteAdded.tsx +++ b/web/packages/design/src/Icon/Icons/NoteAdded.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function NoteAdded({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const NoteAdded = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Notification.tsx b/web/packages/design/src/Icon/Icons/Notification.tsx index 8525bdb063edc..46e2747a18167 100644 --- a/web/packages/design/src/Icon/Icons/Notification.tsx +++ b/web/packages/design/src/Icon/Icons/Notification.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Notification({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Notification = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/NotificationsActive.tsx b/web/packages/design/src/Icon/Icons/NotificationsActive.tsx index f2e17823b55f4..1dc75203c3169 100644 --- a/web/packages/design/src/Icon/Icons/NotificationsActive.tsx +++ b/web/packages/design/src/Icon/Icons/NotificationsActive.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function NotificationsActive({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const NotificationsActive = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/PaperPlane.tsx b/web/packages/design/src/Icon/Icons/PaperPlane.tsx index da7149819be11..8d1129da5d3f7 100644 --- a/web/packages/design/src/Icon/Icons/PaperPlane.tsx +++ b/web/packages/design/src/Icon/Icons/PaperPlane.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function PaperPlane({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const PaperPlane = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Password.tsx b/web/packages/design/src/Icon/Icons/Password.tsx index 58c61e55fee4b..581b73f8d4324 100644 --- a/web/packages/design/src/Icon/Icons/Password.tsx +++ b/web/packages/design/src/Icon/Icons/Password.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,18 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Password({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Password = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Pencil.tsx b/web/packages/design/src/Icon/Icons/Pencil.tsx index f5365bdb8f34a..e3662f001f29c 100644 --- a/web/packages/design/src/Icon/Icons/Pencil.tsx +++ b/web/packages/design/src/Icon/Icons/Pencil.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Pencil({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Pencil = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Planet.tsx b/web/packages/design/src/Icon/Icons/Planet.tsx index 094830e362e69..886b8f17b68ef 100644 --- a/web/packages/design/src/Icon/Icons/Planet.tsx +++ b/web/packages/design/src/Icon/Icons/Planet.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Planet({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Planet = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Plugs.tsx b/web/packages/design/src/Icon/Icons/Plugs.tsx index 85a0bd37e2524..cfeab94172415 100644 --- a/web/packages/design/src/Icon/Icons/Plugs.tsx +++ b/web/packages/design/src/Icon/Icons/Plugs.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Plugs({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Plugs = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/PlugsConnected.tsx b/web/packages/design/src/Icon/Icons/PlugsConnected.tsx index c5ac04e1bc50a..9e8158669cc8f 100644 --- a/web/packages/design/src/Icon/Icons/PlugsConnected.tsx +++ b/web/packages/design/src/Icon/Icons/PlugsConnected.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function PlugsConnected({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const PlugsConnected = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Plus.tsx b/web/packages/design/src/Icon/Icons/Plus.tsx index 34e2fa1c6b449..7047e72bc4f08 100644 --- a/web/packages/design/src/Icon/Icons/Plus.tsx +++ b/web/packages/design/src/Icon/Icons/Plus.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,10 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Plus({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Plus = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/PowerSwitch.tsx b/web/packages/design/src/Icon/Icons/PowerSwitch.tsx index 67fc1a91675ca..c020f964c482b 100644 --- a/web/packages/design/src/Icon/Icons/PowerSwitch.tsx +++ b/web/packages/design/src/Icon/Icons/PowerSwitch.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function PowerSwitch({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const PowerSwitch = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Printer.tsx b/web/packages/design/src/Icon/Icons/Printer.tsx index 3d21ae29d3ccf..f60d3e8a05c6d 100644 --- a/web/packages/design/src/Icon/Icons/Printer.tsx +++ b/web/packages/design/src/Icon/Icons/Printer.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Printer({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Printer = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Profile.tsx b/web/packages/design/src/Icon/Icons/Profile.tsx index 889c4816c6e77..333340d0ecf15 100644 --- a/web/packages/design/src/Icon/Icons/Profile.tsx +++ b/web/packages/design/src/Icon/Icons/Profile.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Profile({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Profile = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -69,5 +72,5 @@ export function Profile({ size = 24, color, ...otherProps }: IconProps) { d="M3.75 3.75C2.92157 3.75 2.25 4.42157 2.25 5.25V18.75C2.25 19.5784 2.92157 20.25 3.75 20.25H20.25C21.0784 20.25 21.75 19.5784 21.75 18.75V5.25C21.75 4.42157 21.0784 3.75 20.25 3.75H3.75ZM3.75 5.25H20.25V18.75H3.75V5.25Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/PushPin.tsx b/web/packages/design/src/Icon/Icons/PushPin.tsx index 025f6cccc1638..75923a7e06592 100644 --- a/web/packages/design/src/Icon/Icons/PushPin.tsx +++ b/web/packages/design/src/Icon/Icons/PushPin.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function PushPin({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const PushPin = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/PushPinFilled.tsx b/web/packages/design/src/Icon/Icons/PushPinFilled.tsx index 18d06de5014de..e1c7faa378d68 100644 --- a/web/packages/design/src/Icon/Icons/PushPinFilled.tsx +++ b/web/packages/design/src/Icon/Icons/PushPinFilled.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function PushPinFilled({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const PushPinFilled = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Question.tsx b/web/packages/design/src/Icon/Icons/Question.tsx index 6596b7ae58790..1c6cde3a1d45c 100644 --- a/web/packages/design/src/Icon/Icons/Question.tsx +++ b/web/packages/design/src/Icon/Icons/Question.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Question({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Question = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -64,5 +67,5 @@ export function Question({ size = 24, color, ...otherProps }: IconProps) { d="M12 2.25C6.61522 2.25 2.25 6.61522 2.25 12C2.25 17.3848 6.61522 21.75 12 21.75C17.3848 21.75 21.75 17.3848 21.75 12C21.75 6.61522 17.3848 2.25 12 2.25ZM3.75 12C3.75 7.44365 7.44365 3.75 12 3.75C16.5563 3.75 20.25 7.44365 20.25 12C20.25 16.5563 16.5563 20.25 12 20.25C7.44365 20.25 3.75 16.5563 3.75 12Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Refresh.tsx b/web/packages/design/src/Icon/Icons/Refresh.tsx index 04e7a78e54682..a69d03c9ecdca 100644 --- a/web/packages/design/src/Icon/Icons/Refresh.tsx +++ b/web/packages/design/src/Icon/Icons/Refresh.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,15 +50,16 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Refresh({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Refresh = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Restore.tsx b/web/packages/design/src/Icon/Icons/Restore.tsx index 9ebf98fec530f..92b9e6532d2d4 100644 --- a/web/packages/design/src/Icon/Icons/Restore.tsx +++ b/web/packages/design/src/Icon/Icons/Restore.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,16 +50,17 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Restore({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Restore = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/RocketLaunch.tsx b/web/packages/design/src/Icon/Icons/RocketLaunch.tsx index 500dc5d066453..018328c2848b0 100644 --- a/web/packages/design/src/Icon/Icons/RocketLaunch.tsx +++ b/web/packages/design/src/Icon/Icons/RocketLaunch.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function RocketLaunch({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const RocketLaunch = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Rows.tsx b/web/packages/design/src/Icon/Icons/Rows.tsx index 7abbbbe61f1c1..7b6d34f119846 100644 --- a/web/packages/design/src/Icon/Icons/Rows.tsx +++ b/web/packages/design/src/Icon/Icons/Rows.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Rows({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Rows = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Ruler.tsx b/web/packages/design/src/Icon/Icons/Ruler.tsx index 6385435788595..3b9a433db7af9 100644 --- a/web/packages/design/src/Icon/Icons/Ruler.tsx +++ b/web/packages/design/src/Icon/Icons/Ruler.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Ruler({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Ruler = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Run.tsx b/web/packages/design/src/Icon/Icons/Run.tsx index 1849f30733505..c8151a8c4c28c 100644 --- a/web/packages/design/src/Icon/Icons/Run.tsx +++ b/web/packages/design/src/Icon/Icons/Run.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Run({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Run = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Scan.tsx b/web/packages/design/src/Icon/Icons/Scan.tsx index 3958fcb89978f..1e18137fe68d2 100644 --- a/web/packages/design/src/Icon/Icons/Scan.tsx +++ b/web/packages/design/src/Icon/Icons/Scan.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,19 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Scan({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Scan = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Server.tsx b/web/packages/design/src/Icon/Icons/Server.tsx index 4cf45cde553c3..b07e3d373b459 100644 --- a/web/packages/design/src/Icon/Icons/Server.tsx +++ b/web/packages/design/src/Icon/Icons/Server.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Server({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Server = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Share.tsx b/web/packages/design/src/Icon/Icons/Share.tsx index 1d3ee4fc17e86..525bc3e8e312f 100644 --- a/web/packages/design/src/Icon/Icons/Share.tsx +++ b/web/packages/design/src/Icon/Icons/Share.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Share({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Share = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ShieldCheck.tsx b/web/packages/design/src/Icon/Icons/ShieldCheck.tsx index 81e0e07c06784..3972e434d1d95 100644 --- a/web/packages/design/src/Icon/Icons/ShieldCheck.tsx +++ b/web/packages/design/src/Icon/Icons/ShieldCheck.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ShieldCheck({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ShieldCheck = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/ShieldWarning.tsx b/web/packages/design/src/Icon/Icons/ShieldWarning.tsx index efe86555542ae..b8a1c10c32e4c 100644 --- a/web/packages/design/src/Icon/Icons/ShieldWarning.tsx +++ b/web/packages/design/src/Icon/Icons/ShieldWarning.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function ShieldWarning({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const ShieldWarning = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -64,5 +67,5 @@ export function ShieldWarning({ size = 24, color, ...otherProps }: IconProps) { d="M3.43934 4.18934C3.72064 3.90804 4.10217 3.75 4.5 3.75H19.5C19.8978 3.75 20.2794 3.90804 20.5607 4.18934C20.842 4.47065 21 4.85218 21 5.25V10.7597C21 19.1792 13.8537 21.9599 12.4706 22.4203C12.1656 22.5244 11.8348 22.5244 11.5298 22.4204C10.1444 21.9611 3 19.1829 3 10.7616V5.25C3 4.85217 3.15804 4.47064 3.43934 4.18934ZM19.5 5.25L4.5 5.25L4.5 10.7616C4.5 18.112 10.6965 20.5635 12 20.996C13.3064 20.5606 19.5 18.1058 19.5 10.7597V5.25Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Sliders.tsx b/web/packages/design/src/Icon/Icons/Sliders.tsx index 29d00763a60db..b6e0d96106c65 100644 --- a/web/packages/design/src/Icon/Icons/Sliders.tsx +++ b/web/packages/design/src/Icon/Icons/Sliders.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Sliders({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Sliders = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/SlidersVertical.tsx b/web/packages/design/src/Icon/Icons/SlidersVertical.tsx index 1d5516cea0881..4f2fd67ddcf19 100644 --- a/web/packages/design/src/Icon/Icons/SlidersVertical.tsx +++ b/web/packages/design/src/Icon/Icons/SlidersVertical.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,17 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function SlidersVertical({ - size = 24, - color, - ...otherProps -}: IconProps) { - return ( +export const SlidersVertical = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Speed.tsx b/web/packages/design/src/Icon/Icons/Speed.tsx index b13e226a9ae67..62316da943096 100644 --- a/web/packages/design/src/Icon/Icons/Speed.tsx +++ b/web/packages/design/src/Icon/Icons/Speed.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Speed({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Speed = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Spinner.tsx b/web/packages/design/src/Icon/Icons/Spinner.tsx index a2531e04bfa83..b659dc396ccc3 100644 --- a/web/packages/design/src/Icon/Icons/Spinner.tsx +++ b/web/packages/design/src/Icon/Icons/Spinner.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Spinner({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Spinner = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/SquaresFour.tsx b/web/packages/design/src/Icon/Icons/SquaresFour.tsx index f92dee14796cb..43a44c4841bc2 100644 --- a/web/packages/design/src/Icon/Icons/SquaresFour.tsx +++ b/web/packages/design/src/Icon/Icons/SquaresFour.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function SquaresFour({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const SquaresFour = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Stars.tsx b/web/packages/design/src/Icon/Icons/Stars.tsx index e2a3772248c42..4017a951bd74a 100644 --- a/web/packages/design/src/Icon/Icons/Stars.tsx +++ b/web/packages/design/src/Icon/Icons/Stars.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Stars({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Stars = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Sun.tsx b/web/packages/design/src/Icon/Icons/Sun.tsx index 5d6a010667989..f813885be84f2 100644 --- a/web/packages/design/src/Icon/Icons/Sun.tsx +++ b/web/packages/design/src/Icon/Icons/Sun.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Sun({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Sun = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/SyncAlt.tsx b/web/packages/design/src/Icon/Icons/SyncAlt.tsx index ef97c1bbf8bd5..22b5755d2f84a 100644 --- a/web/packages/design/src/Icon/Icons/SyncAlt.tsx +++ b/web/packages/design/src/Icon/Icons/SyncAlt.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,16 +50,17 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function SyncAlt({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const SyncAlt = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Table.tsx b/web/packages/design/src/Icon/Icons/Table.tsx index eeb8c2c490a2e..8a67dfdaecc70 100644 --- a/web/packages/design/src/Icon/Icons/Table.tsx +++ b/web/packages/design/src/Icon/Icons/Table.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,19 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Table({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Table = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Tablet.tsx b/web/packages/design/src/Icon/Icons/Tablet.tsx index 19bc87a1b4588..97e2de34ec170 100644 --- a/web/packages/design/src/Icon/Icons/Tablet.tsx +++ b/web/packages/design/src/Icon/Icons/Tablet.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Tablet({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Tablet = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Tags.tsx b/web/packages/design/src/Icon/Icons/Tags.tsx index 5eda6492f1d3c..24fcd68ab51b7 100644 --- a/web/packages/design/src/Icon/Icons/Tags.tsx +++ b/web/packages/design/src/Icon/Icons/Tags.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Tags({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Tags = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Terminal.tsx b/web/packages/design/src/Icon/Icons/Terminal.tsx index d97b57aafad5f..bcbf267204420 100644 --- a/web/packages/design/src/Icon/Icons/Terminal.tsx +++ b/web/packages/design/src/Icon/Icons/Terminal.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Terminal({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Terminal = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Trash.tsx b/web/packages/design/src/Icon/Icons/Trash.tsx index 91cc228a8905b..680b7d4e88b12 100644 --- a/web/packages/design/src/Icon/Icons/Trash.tsx +++ b/web/packages/design/src/Icon/Icons/Trash.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Trash({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Trash = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Twitter.tsx b/web/packages/design/src/Icon/Icons/Twitter.tsx index 534beaa7f8a95..f69a0902f30a3 100644 --- a/web/packages/design/src/Icon/Icons/Twitter.tsx +++ b/web/packages/design/src/Icon/Icons/Twitter.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Twitter({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Twitter = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Unarchive.tsx b/web/packages/design/src/Icon/Icons/Unarchive.tsx index 9a94dcb3efe58..db582586399d4 100644 --- a/web/packages/design/src/Icon/Icons/Unarchive.tsx +++ b/web/packages/design/src/Icon/Icons/Unarchive.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Unarchive({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Unarchive = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Unlink.tsx b/web/packages/design/src/Icon/Icons/Unlink.tsx index b37625602faf0..4e4b6ed8d5f51 100644 --- a/web/packages/design/src/Icon/Icons/Unlink.tsx +++ b/web/packages/design/src/Icon/Icons/Unlink.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Unlink({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Unlink = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -63,5 +66,5 @@ export function Unlink({ size = 24, color, ...otherProps }: IconProps) { - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Unlock.tsx b/web/packages/design/src/Icon/Icons/Unlock.tsx index 204504d9fe7ba..6f3b94037c557 100644 --- a/web/packages/design/src/Icon/Icons/Unlock.tsx +++ b/web/packages/design/src/Icon/Icons/Unlock.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Unlock({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Unlock = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Upload.tsx b/web/packages/design/src/Icon/Icons/Upload.tsx index 03b2672311be8..1cf22e0f4e46d 100644 --- a/web/packages/design/src/Icon/Icons/Upload.tsx +++ b/web/packages/design/src/Icon/Icons/Upload.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,16 +50,17 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Upload({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const Upload = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/UsbDrive.tsx b/web/packages/design/src/Icon/Icons/UsbDrive.tsx index 92edd134e15da..edc1394598921 100644 --- a/web/packages/design/src/Icon/Icons/UsbDrive.tsx +++ b/web/packages/design/src/Icon/Icons/UsbDrive.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function UsbDrive({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const UsbDrive = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -64,5 +67,5 @@ export function UsbDrive({ size = 24, color, ...otherProps }: IconProps) { d="M15.4782 2.59067L11.6295 6.43932C10.738 6.32356 9.8045 6.60808 9.11968 7.29289L2.96022 13.4524C1.78865 14.6239 1.78865 16.5234 2.96022 17.695L6.30388 21.0386C7.47545 22.2102 9.37494 22.2102 10.5465 21.0386L16.706 14.8792C17.3255 14.2596 17.6174 13.4365 17.5817 12.6252L21.5472 8.65971C22.3282 7.87866 22.3282 6.61233 21.5472 5.83128L18.3066 2.59067C17.5255 1.80962 16.2592 1.80962 15.4782 2.59067ZM20.4865 6.89194L17.2459 3.65133C17.0507 3.45607 16.7341 3.45607 16.5388 3.65133L13.1212 7.06898L13.1612 7.10897C13.2303 7.16668 13.2974 7.22799 13.3623 7.29289L16.706 10.6365C16.7709 10.7014 16.8322 10.7686 16.8899 10.8377L17.0689 11.0167L20.4865 7.59905C20.6818 7.40378 20.6818 7.0872 20.4865 6.89194ZM10.1803 8.35355C10.7201 7.8138 11.5689 7.77138 12.1572 8.22631L15.7726 11.8417C16.2275 12.43 16.1851 13.2788 15.6453 13.8185L9.48586 19.978C8.90007 20.5638 7.95032 20.5638 7.36454 19.978L4.02088 16.6343C3.4351 16.0485 3.4351 15.0988 4.02088 14.513L10.1803 8.35355Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/User.tsx b/web/packages/design/src/Icon/Icons/User.tsx index 788281c91cda3..c215423670e17 100644 --- a/web/packages/design/src/Icon/Icons/User.tsx +++ b/web/packages/design/src/Icon/Icons/User.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function User({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const User = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/UserAdd.tsx b/web/packages/design/src/Icon/Icons/UserAdd.tsx index e3e5765d47ab9..f04141fd86832 100644 --- a/web/packages/design/src/Icon/Icons/UserAdd.tsx +++ b/web/packages/design/src/Icon/Icons/UserAdd.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function UserAdd({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const UserAdd = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/UserCircleGear.tsx b/web/packages/design/src/Icon/Icons/UserCircleGear.tsx index c17c37454bab3..16be34e4bb583 100644 --- a/web/packages/design/src/Icon/Icons/UserCircleGear.tsx +++ b/web/packages/design/src/Icon/Icons/UserCircleGear.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function UserCircleGear({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const UserCircleGear = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/UserFocus.tsx b/web/packages/design/src/Icon/Icons/UserFocus.tsx index de99762dcc5aa..f7e68aea23336 100644 --- a/web/packages/design/src/Icon/Icons/UserFocus.tsx +++ b/web/packages/design/src/Icon/Icons/UserFocus.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function UserFocus({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const UserFocus = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -66,5 +69,5 @@ export function UserFocus({ size = 24, color, ...otherProps }: IconProps) { d="M8.25 10.5C8.25 8.42893 9.92893 6.75 12 6.75C14.0711 6.75 15.75 8.42893 15.75 10.5C15.75 11.5981 15.278 12.5859 14.5259 13.2717C14.6355 13.319 14.7439 13.3695 14.851 13.423C15.7362 13.8656 16.5062 14.5083 17.1 15.3C17.3486 15.6314 17.2814 16.1015 16.95 16.35C16.6187 16.5985 16.1486 16.5314 15.9 16.2C15.446 15.5945 14.8571 15.1031 14.1802 14.7647C13.5033 14.4262 12.7569 14.25 12 14.25C11.2432 14.25 10.4968 14.4262 9.81988 14.7647C9.14296 15.1031 8.55414 15.5945 8.10004 16.2C7.85152 16.5314 7.38141 16.5985 7.05004 16.35C6.71867 16.1015 6.65152 15.6314 6.90004 15.3C7.49386 14.5083 8.26385 13.8656 9.14906 13.423C9.25613 13.3695 9.36453 13.3191 9.47413 13.2718C8.72198 12.5859 8.25 11.5981 8.25 10.5ZM12 8.25C10.7574 8.25 9.75 9.25736 9.75 10.5C9.75 11.7426 10.7574 12.75 12 12.75C13.2426 12.75 14.25 11.7426 14.25 10.5C14.25 9.25736 13.2426 8.25 12 8.25Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/UserIdBadge.tsx b/web/packages/design/src/Icon/Icons/UserIdBadge.tsx index f340c591ea015..a6e02ffbb5599 100644 --- a/web/packages/design/src/Icon/Icons/UserIdBadge.tsx +++ b/web/packages/design/src/Icon/Icons/UserIdBadge.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function UserIdBadge({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const UserIdBadge = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/UserList.tsx b/web/packages/design/src/Icon/Icons/UserList.tsx index 49b90b8042aea..0105fc61cbee9 100644 --- a/web/packages/design/src/Icon/Icons/UserList.tsx +++ b/web/packages/design/src/Icon/Icons/UserList.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function UserList({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const UserList = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Users.tsx b/web/packages/design/src/Icon/Icons/Users.tsx index 93b0313cc4e6e..8afd0f8c86000 100644 --- a/web/packages/design/src/Icon/Icons/Users.tsx +++ b/web/packages/design/src/Icon/Icons/Users.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Users({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Users = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/UsersTriple.tsx b/web/packages/design/src/Icon/Icons/UsersTriple.tsx index 314f934eaddc1..d1a58ea40fd72 100644 --- a/web/packages/design/src/Icon/Icons/UsersTriple.tsx +++ b/web/packages/design/src/Icon/Icons/UsersTriple.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function UsersTriple({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const UsersTriple = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Vault.tsx b/web/packages/design/src/Icon/Icons/Vault.tsx index e4389813ca53c..197c030ed97b2 100644 --- a/web/packages/design/src/Icon/Icons/Vault.tsx +++ b/web/packages/design/src/Icon/Icons/Vault.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,14 +50,20 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Vault({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Vault = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( + - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/VideoGame.tsx b/web/packages/design/src/Icon/Icons/VideoGame.tsx index 921e5c6873525..6b97c2fab375e 100644 --- a/web/packages/design/src/Icon/Icons/VideoGame.tsx +++ b/web/packages/design/src/Icon/Icons/VideoGame.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function VideoGame({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const VideoGame = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( @@ -64,5 +67,5 @@ export function VideoGame({ size = 24, color, ...otherProps }: IconProps) { d="M7.86299 3.75L7.86465 3.75H16.125C17.6168 3.75 19.0475 4.34263 20.1024 5.39753C20.9281 6.22321 21.4706 7.27916 21.6674 8.41444L23.1986 16.2894C23.323 16.9962 23.2189 17.7242 22.9014 18.3678C22.5839 19.0114 22.0696 19.5371 21.433 19.8684C20.7965 20.1998 20.0709 20.3197 19.3616 20.2106C18.6523 20.1015 17.9962 19.7692 17.4887 19.2618C17.4774 19.2506 17.4666 19.2391 17.4561 19.2272L13.7321 15H10.2678L6.54398 19.227C6.5335 19.2389 6.52264 19.2505 6.51142 19.2617C6.00386 19.769 5.34779 20.1014 4.6385 20.2104C3.9292 20.3195 3.2036 20.1997 2.56705 19.8683C1.93051 19.5369 1.41616 19.0113 1.09869 18.3677C0.781228 17.7241 0.677175 16.9961 0.801643 16.2893L0.803948 16.2762L2.33656 8.39453C2.56636 7.09633 3.24488 5.92 4.25375 5.07105C5.26441 4.2206 6.54212 3.75293 7.86299 3.75ZM15.7312 15L18.5646 18.2163C18.8444 18.4895 19.2027 18.6685 19.5896 18.728C19.9836 18.7886 20.3868 18.722 20.7404 18.5379C21.094 18.3538 21.3798 18.0618 21.5561 17.7043C21.7316 17.3485 21.7897 16.9464 21.7222 16.5556L20.9032 12.3431C20.6776 12.7063 20.4096 13.0453 20.1024 13.3525C19.0475 14.4074 17.6168 15 16.125 15H15.7312ZM20.1839 8.63936C20.1856 8.65107 20.1876 8.6628 20.1898 8.67454L20.1917 8.68429C20.2302 8.91101 20.25 9.14199 20.25 9.375C20.25 10.469 19.8154 11.5182 19.0418 12.2918C18.2682 13.0654 17.219 13.5 16.125 13.5H9.92903C9.71363 13.5 9.50864 13.5926 9.36626 13.7542L5.4355 18.2161C5.15572 18.4893 4.79736 18.6684 4.4105 18.7279C4.01644 18.7885 3.61333 18.7219 3.25969 18.5378C2.90606 18.3537 2.62031 18.0617 2.44394 17.7041C2.26847 17.3484 2.21035 16.9462 2.27786 16.5555L3.81024 8.6744L3.81271 8.66106C3.98035 7.70704 4.47839 6.84244 5.21954 6.21877C5.96048 5.59527 6.89716 5.25234 7.86551 5.25H16.125C17.219 5.25 18.2682 5.6846 19.0418 6.45819C19.6397 7.05611 20.0351 7.8187 20.1839 8.63936Z" /> - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/VolumeUp.tsx b/web/packages/design/src/Icon/Icons/VolumeUp.tsx index e73821d0d469a..8b3d1b21618f8 100644 --- a/web/packages/design/src/Icon/Icons/VolumeUp.tsx +++ b/web/packages/design/src/Icon/Icons/VolumeUp.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function VolumeUp({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const VolumeUp = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/VpnKey.tsx b/web/packages/design/src/Icon/Icons/VpnKey.tsx index 6276a9ec4ee24..f6a2e10267bf7 100644 --- a/web/packages/design/src/Icon/Icons/VpnKey.tsx +++ b/web/packages/design/src/Icon/Icons/VpnKey.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,13 +50,14 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function VpnKey({ size = 24, color, ...otherProps }: IconProps) { - return ( +export const VpnKey = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( - ); -} + ) +); diff --git a/web/packages/design/src/Icon/Icons/Wand.tsx b/web/packages/design/src/Icon/Icons/Wand.tsx index 2cfa8a811f68f..8e1ba8801f0a6 100644 --- a/web/packages/design/src/Icon/Icons/Wand.tsx +++ b/web/packages/design/src/Icon/Icons/Wand.tsx @@ -40,6 +40,8 @@ SOFTWARE. */ +import { forwardRef } from 'react'; + import { Icon, IconProps } from '../Icon'; /* @@ -48,9 +50,15 @@ THIS FILE IS GENERATED. DO NOT EDIT. */ -export function Wand({ size = 24, color, ...otherProps }: IconProps) { - return ( - +export const Wand = forwardRef( + ({ size = 24, color, ...otherProps }, ref) => ( +
On the first render, the view should be scrolled to the icon.