v2

package
v0.1.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 5, 2024 License: Apache-2.0 Imports: 14 Imported by: 1

Documentation

Index

Constants

View Source
const (
	TagApprove string = "approve"
	TagRefresh string = "refresh"
)
View Source
const (
	ErrApplicationInvalid     api.ErrorType = "application-invalid"
	ErrApplicationNotFound    api.ErrorType = "application-not-found"
	ErrApplicationExists      api.ErrorType = "application-exists"
	ErrScenarioInvalid        api.ErrorType = "scenario-invalid"
	ErrScenarioNotFound       api.ErrorType = "scenario-not-found"
	ErrScenarioExists         api.ErrorType = "scenario-exists"
	ErrScanInvalid            api.ErrorType = "scan-invalid"
	ErrActivityInvalid        api.ErrorType = "activity-invalid"
	ErrActivityRateLimited    api.ErrorType = "activity-rate-limited"
	ErrRecommendationInvalid  api.ErrorType = "recommendation-invalid"
	ErrRecommendationNotFound api.ErrorType = "recommendation-not-found"
	ErrClusterNotFound        api.ErrorType = "cluster-not-found"
)

Variables

This section is empty.

Functions

func SplitScenarioName added in v0.0.25

func SplitScenarioName(name string) (ApplicationName, ScenarioName)

Types

type API

type API interface {
	// CheckEndpoint verifies we can talk to the backend.
	CheckEndpoint(ctx context.Context) (api.Metadata, error)

	// ListApplications gets a list of existing applications for an authorized request.
	ListApplications(ctx context.Context, q ApplicationListQuery) (ApplicationList, error)
	// ListApplicationsByPage returns single page of applications identified by the supplied URL.
	ListApplicationsByPage(ctx context.Context, u string) (ApplicationList, error)
	// CreateApplication creates a new application.
	CreateApplication(ctx context.Context, app Application) (api.Metadata, error)
	// CreateApplicationByName creates a new application.
	CreateApplicationByName(ctx context.Context, n ApplicationName, app Application) (api.Metadata, error)
	// GetApplication retrieves an application.
	GetApplication(ctx context.Context, u string) (Application, error)
	// GetApplicationByName retrieves an application.
	GetApplicationByName(ctx context.Context, n ApplicationName) (Application, error)
	// UpdateApplication updates an application.
	UpdateApplication(ctx context.Context, u string, app Application) (api.Metadata, error)
	// UpdateApplicationByName updates or creates an application.
	UpdateApplicationByName(ctx context.Context, n ApplicationName, app Application) (api.Metadata, error)
	// DeleteApplication deletes an application.
	DeleteApplication(ctx context.Context, u string) error

	// ListScenarios lists configured scenarios for an application.
	// Deprecated: scenarios should no longer be used.
	ListScenarios(ctx context.Context, u string, q ScenarioListQuery) (ScenarioList, error)
	// CreateScenario creates a scenario.
	// Deprecated: scenarios should no longer be used.
	CreateScenario(ctx context.Context, u string, scn Scenario) (api.Metadata, error)
	// CreateScenarioByName creates a scenario.
	// Deprecated: scenarios should no longer be used.
	CreateScenarioByName(ctx context.Context, u string, n ScenarioName, scn Scenario) (Scenario, error)
	// GetScenario retrieves a scenario.
	// Deprecated: scenarios should no longer be used.
	GetScenario(ctx context.Context, u string) (Scenario, error)
	// GetScenarioByName retrieves a scenario by name.
	// Deprecated: scenarios should no longer be used.
	GetScenarioByName(ctx context.Context, u string, n ScenarioName) (Scenario, error)
	// UpdateScenario updates or creates a scenario.
	// Deprecated: scenarios should no longer be used.
	UpdateScenario(ctx context.Context, u string, scn Scenario) (Scenario, error)
	// UpdateScenarioByName updates or creates a scenario.
	// Deprecated: scenarios should no longer be used.
	UpdateScenarioByName(ctx context.Context, u string, n ScenarioName, scn Scenario) (Scenario, error)
	// DeleteScenario deletes a scenario.
	// Deprecated: scenarios should no longer be used.
	DeleteScenario(ctx context.Context, u string) error
	// PatchScenario updates attributes on a scenario.
	// Deprecated: scenarios should no longer be used.
	PatchScenario(ctx context.Context, u string, scn Scenario) error

	// GetTemplate gets the application scenario template.
	GetTemplate(ctx context.Context, u string) (Template, error)
	// UpdateTemplate records or updates scenario template.
	UpdateTemplate(ctx context.Context, u string, s Template) error
	// PatchTemplate updates a partial scenario template.
	PatchTemplate(ctx context.Context, u string, s Template) error

	// ListActivity gets activity feed for an application.
	ListActivity(ctx context.Context, u string, q ActivityFeedQuery) (ActivityFeed, error)
	// CreateActivity creates application activity.
	CreateActivity(ctx context.Context, u string, a Activity) error
	// DeleteActivity resolves application activity.
	DeleteActivity(ctx context.Context, u string) error
	// PatchApplicationActivity updates application activity.
	PatchApplicationActivity(ctx context.Context, u string, a ActivityPatchRequest) error

	// SubscribeActivity returns a subscriber for the activity feed.
	SubscribeActivity(ctx context.Context, q ActivityFeedQuery) (Subscriber, error)

	// CreateRecommendation creates an application recommendation using the most recently published values.
	CreateRecommendation(ctx context.Context, u string) (api.Metadata, error)
	// GetRecommendation retrieves a recommendation.
	GetRecommendation(ctx context.Context, u string) (Recommendation, error)
	// ListRecommendations lists recommendations and recommendation configuration for an application.
	ListRecommendations(ctx context.Context, u string) (RecommendationList, error)
	// PatchRecommendations updates recommendation configuration.
	PatchRecommendations(ctx context.Context, u string, details RecommendationList) error

	// GetCluster retrieves a cluster.
	GetCluster(ctx context.Context, u string) (Cluster, error)
	// GetClusterByName retrieves a cluster.
	GetClusterByName(ctx context.Context, n ClusterName) (Cluster, error)
	// ListClusters lists clusters.
	ListClusters(ctx context.Context, q ClusterListQuery) (ClusterList, error)
	// PatchCluster updates a cluster title.
	PatchCluster(ctx context.Context, u string, c ClusterTitle) error
	// DeleteCluster deletes a cluster.
	DeleteCluster(ctx context.Context, u string) error
}

func NewAPI

func NewAPI(client api.Client) API

type Activity

type Activity struct {
	api.Metadata `json:"-"`
	Run          *RunActivity     `json:"run,omitempty"`
	Scan         *ScanActivity    `json:"scan,omitempty"`
	Approve      *ApproveActivity `json:"approve,omitempty"`
	Refresh      *RefreshActivity `json:"refresh,omitempty"`
}

type ActivityExtension

type ActivityExtension struct {
	ActivityFailure
}

type ActivityFailure

type ActivityFailure struct {
	FailureReason  string `json:"failure_reason,omitempty"`
	FailureMessage string `json:"failure_message,omitempty"`
}

type ActivityFeed

type ActivityFeed struct {
	HomePageURL string         `json:"home_page_url,omitempty"`
	FeedURL     string         `json:"feed_url,omitempty"`
	NextURL     string         `json:"next_url,omitempty"`
	Hubs        []ActivityHub  `json:"hubs,omitempty"`
	Items       []ActivityItem `json:"items"`
}

func (*ActivityFeed) SetBaseURL

func (af *ActivityFeed) SetBaseURL(u string)

SetBaseURL resolves the URLs on the activity feed against a supplied base. Typically, the URL used to fetch the feed, the feed's `feed_url` field, and the base URL should all match; however, it may be the case that the `feed_url` field is returned as a relative URL (the JSON Feed spec does not specify a behavior in this regard).

type ActivityFeedQuery

type ActivityFeedQuery struct {
	Query map[string][]string
}

func (*ActivityFeedQuery) SetType

func (q *ActivityFeedQuery) SetType(t ...string)

type ActivityHub

type ActivityHub struct {
	Type string `json:"type"`
	URL  string `json:"url"`
}

type ActivityItem

type ActivityItem struct {
	ID            string             `json:"id"`
	URL           string             `json:"url,omitempty"`
	ExternalURL   string             `json:"external_url,omitempty"`
	Title         string             `json:"title,omitempty"`
	DatePublished time.Time          `json:"date_published,omitempty"`
	DateModified  time.Time          `json:"date_modified,omitempty"`
	Tags          []string           `json:"tags,omitempty"`
	StormForge    *ActivityExtension `json:"_stormforge,omitempty"`
}

func (*ActivityItem) HasTag

func (ai *ActivityItem) HasTag(tag string) bool

type ActivityPatchRequest added in v0.1.18

type ActivityPatchRequest struct {
	Title string `json:"title"`
	// Data is a JSON-serializable value for internal metadata about the Activity
	Data any `json:"_stormforge,omitempty"`
}

type Application

type Application struct {
	api.Metadata `json:"-"`
	Name         ApplicationName `json:"name,omitempty"`
	DisplayName  string          `json:"title,omitempty"` // TODO This doesn't seem to get set
	Resources    []Resource      `json:"resources,omitempty"`
	CreatedAt    *time.Time      `json:"createdAt,omitempty"`
}

type ApplicationItem

type ApplicationItem struct {
	Application
	// The number of scenarios associated with this application.
	ScenarioCount   int                 `json:"scenarioCount,omitempty"`
	LastDeployedAt  *time.Time          `json:"lastDeployedAt,omitempty"`
	Recommendations RecommendationsMode `json:"recommendations,omitempty"`
}

func (*ApplicationItem) UnmarshalJSON

func (ai *ApplicationItem) UnmarshalJSON(b []byte) error

type ApplicationList

type ApplicationList struct {
	// The application list metadata.
	api.Metadata `json:"-"`
	// The total number of items in the collection.
	TotalCount int `json:"totalCount,omitempty"`
	// The list of applications.
	Applications []ApplicationItem `json:"applications"`
}

type ApplicationListQuery

type ApplicationListQuery struct{ api.IndexQuery }

type ApplicationName

type ApplicationName string

ApplicationName represents a name token used to identify an application.

func SplitRecommendationName added in v0.0.25

func SplitRecommendationName(name string) (ApplicationName, string)

func (ApplicationName) String

func (n ApplicationName) String() string

type ApproveActivity added in v0.0.23

type ApproveActivity struct {
	Recommendation string `json:"recommendation"`
	ActivityFailure
}

type BackfillProgress added in v0.1.2

type BackfillProgress struct {
	Timestamp time.Time `json:"timestamp"`
}

type Bounds added in v0.1.2

type Bounds struct {
	Limits            *BoundsRange `json:"limits,omitempty"`
	Requests          *BoundsRange `json:"requests,omitempty"`
	TargetUtilization *BoundsRange `json:"targetUtilization,omitempty"`
}

type BoundsRange added in v0.1.2

type BoundsRange struct {
	Max *ResourceList `json:"max,omitempty"`
	Min *ResourceList `json:"min,omitempty"`
}

type Cluster added in v0.0.25

type Cluster struct {
	api.Metadata           `json:"-"`
	Name                   ClusterName `json:"name,omitempty"`
	CreatedAt              *time.Time  `json:"created,omitempty"`
	OptimizeProVersion     string      `json:"optimizeProVersion,omitempty"`
	OptimizeLiveVersion    string      `json:"optimizeLiveVersion,omitempty"`
	PerformanceTestVersion string      `json:"performanceTestVersion,omitempty"`
	KubernetesVersion      string      `json:"kubernetesVersion,omitempty"`
	LastSeen               *time.Time  `json:"lastSeen,omitempty"`
}

type ClusterItem added in v0.0.25

type ClusterItem struct {
	Cluster
}

func (*ClusterItem) UnmarshalJSON added in v0.0.25

func (ci *ClusterItem) UnmarshalJSON(b []byte) error

type ClusterList added in v0.0.25

type ClusterList struct {
	// The cluster list metadata.
	api.Metadata `json:"-"`
	// The total number of items in the collection.
	TotalCount int `json:"totalCount,omitempty"`
	// The list of clusters.
	Items []ClusterItem `json:"items"`
}

type ClusterListQuery added in v0.0.25

type ClusterListQuery struct{ api.IndexQuery }

func (*ClusterListQuery) SetModules added in v0.0.25

func (q *ClusterListQuery) SetModules(modules ...ClusterModule)

type ClusterModule added in v0.0.25

type ClusterModule string
const (
	ClusterRecommendations ClusterModule = "recommendations"
	ClusterScenarios       ClusterModule = "scenarios"
)

type ClusterName added in v0.0.25

type ClusterName string

ClusterName represents a name token used to identify a cluster.

func (ClusterName) String added in v0.0.25

func (n ClusterName) String() string

type ClusterTitle added in v0.0.25

type ClusterTitle struct {
	Title string `json:"title"`
}

type Configuration added in v0.1.2

type Configuration struct {
	ContainerResources *ContainerResources `json:"containerResources,omitempty"`
}

func MergeConfigurations added in v0.1.5

func MergeConfigurations(a, b *Configuration) (*Configuration, error)

MergeConfigurations combines the supplied configurations into a new configuration.

type ContainerResources added in v0.1.2

type ContainerResources struct {
	Selector          string        `json:"selector,omitempty"`
	Interval          api.Duration  `json:"interval,omitempty"`
	TargetUtilization *ResourceList `json:"targetUtilization,omitempty"`
	Tolerance         *ResourceList `json:"tolerance,omitempty"`
	Bounds            *Bounds       `json:"bounds,omitempty"`
	LimitRequestRatio *ResourceList `json:"limitRequestRatio,omitempty"`
}

type DeployConfiguration added in v0.0.23

type DeployConfiguration struct {
	Mode                   RecommendationsMode `json:"mode,omitempty"`
	Interval               api.Duration        `json:"interval,omitempty"`
	Limits                 []LimitRangeItem    `json:"limits,omitempty"`
	MaxRecommendationRatio *ResourceList       `json:"maxRecommendationRatio,omitempty"`
	Clusters               []string            `json:"clusters,omitempty"`
}

type LimitRangeItem added in v0.0.25

type LimitRangeItem struct {
	Type string        `json:"type,omitempty"`
	Max  *ResourceList `json:"max,omitempty"`
	Min  *ResourceList `json:"min,omitempty"`
}

type Lister added in v0.0.21

type Lister struct {
	// API is the Application API used to fetch objects.
	API API
	// BatchSize overrides the default batch size for fetching lists.
	BatchSize int
}

Lister is a helper to individually visit all items in a list (even across page boundaries).

func (*Lister) ForEachApplication added in v0.0.21

func (l *Lister) ForEachApplication(ctx context.Context, q ApplicationListQuery, f func(*ApplicationItem) error) error

ForEachApplication iterates over all the applications matching the supplied query.

func (*Lister) ForEachCluster added in v0.0.25

func (l *Lister) ForEachCluster(ctx context.Context, q ClusterListQuery, f func(item *ClusterItem) error) error

ForEachCluster iterates over all the clusters.

func (*Lister) ForEachNamedApplication added in v0.0.25

func (l *Lister) ForEachNamedApplication(ctx context.Context, names []string, ignoreNotFound bool, f func(item *ApplicationItem) error) error

ForEachNamedApplication iterates over all the named applications, optionally ignoring those that do not exist.

func (*Lister) ForEachNamedCluster added in v0.0.25

func (l *Lister) ForEachNamedCluster(ctx context.Context, names []string, ignoreNotFound bool, f func(item *ClusterItem) error) error

ForEachNamedCluster iterates over all the named clusters, optionally ignoring those that do not exist.

func (*Lister) ForEachNamedRecommendation added in v0.0.25

func (l *Lister) ForEachNamedRecommendation(ctx context.Context, names []string, ignoreNotFound bool, f func(item *RecommendationItem) error) error

ForEachNamedRecommendation iterates over all the named recommendations, optionally ignoring those that do not exist.

func (*Lister) ForEachNamedScenario added in v0.0.25

func (l *Lister) ForEachNamedScenario(ctx context.Context, names []string, ignoreNotFound bool, f func(item *ScenarioItem) error) error

ForEachNamedScenario iterates over all the named scenarios, optionally ignoring those that do not exist. Deprecated: scenarios should no longer be used.

func (*Lister) ForEachRecommendation added in v0.0.25

func (l *Lister) ForEachRecommendation(ctx context.Context, app *Application, f func(item *RecommendationItem) error) (err error)

ForEachRecommendation iterates over all the recommendations for an application.

func (*Lister) ForEachScenario added in v0.0.21

func (l *Lister) ForEachScenario(ctx context.Context, app *Application, q ScenarioListQuery, f func(*ScenarioItem) error) (err error)

ForEachScenario iterates over all scenarios for an application matching the supplied query. Deprecated: scenarios should no longer be used.

func (*Lister) GetApplicationByNameOrTitle added in v0.0.22

func (l *Lister) GetApplicationByNameOrTitle(ctx context.Context, name string) (*Application, error)

GetApplicationByNameOrTitle tries to get an application by name and falls back to a linear search over all the applications by title.

func (*Lister) GetScenarioByNameOrTitle added in v0.0.22

func (l *Lister) GetScenarioByNameOrTitle(ctx context.Context, app *Application, name string) (*Scenario, error)

GetScenarioByNameOrTitle tries to get a scenario by name and falls back to a linear search over all the scenarios by title. Deprecated: scenarios should no longer be used

type Parameter added in v0.0.23

type Parameter struct {
	Target             TargetRef     `json:"target"`
	ContainerResources []interface{} `json:"containerResources"`
	HPAResources       []interface{} `json:"hpaResources"`
}

type PollingSubscriber

type PollingSubscriber struct {
	// The API instance used to fetch the feed.
	API API
	// The URL to poll.
	FeedURL string
	// Time between polling requests. Defaults to 30 seconds.
	PollInterval time.Duration
	// Adjust the poll duration by a random amount. Defaults to 1.0, effectively
	// a random amount up to the full poll interval.
	JitterFactor float64
	// Flag indicating that failed activities should still be reported.
	ReportFailedActivities bool // TODO Should this be part of the ActivityFeedQuery?
	// contains filtered or unexported fields
}

PollingSubscriber is a primitive strategy that simply polls for changes.

func (*PollingSubscriber) PollTimer

func (s *PollingSubscriber) PollTimer() *time.Timer

PollTimer returns a new timer for the next polling operation.

func (*PollingSubscriber) Subscribe

func (s *PollingSubscriber) Subscribe(ctx context.Context, ch chan<- ActivityItem) error

Subscribe polls for activity, blocking until the supplied context is finished or a fatal error occurs talking to the activity endpoint.

type Recommendation added in v0.0.23

type Recommendation struct {
	api.Metadata `json:"-"`
	Name         string      `json:"name"`
	DeployedAt   *time.Time  `json:"deployedAt,omitempty"`
	Parameters   []Parameter `json:"parameters,omitempty"`
}

type RecommendationItem added in v0.0.23

type RecommendationItem struct {
	Recommendation
}

func (*RecommendationItem) UnmarshalJSON added in v0.0.23

func (l *RecommendationItem) UnmarshalJSON(b []byte) error

type RecommendationList added in v0.0.23

type RecommendationList struct {
	api.Metadata        `json:"-"`
	DeployConfiguration *DeployConfiguration `json:"deploy,omitempty"`
	Configuration       []Configuration      `json:"configuration,omitempty"`
	BackfillProgress    *BackfillProgress    `json:"backfillProgress,omitempty"`
	Recommendations     []RecommendationItem `json:"recommendations,omitempty"`
}

type RecommendationsMode added in v0.0.23

type RecommendationsMode string
const (
	RecommendationsDisabled RecommendationsMode = "disabled"
	RecommendationsManual   RecommendationsMode = "manual"
	RecommendationsAuto     RecommendationsMode = "auto"
)

func (RecommendationsMode) Enabled added in v0.1.2

func (m RecommendationsMode) Enabled() bool

type RefreshActivity added in v0.0.23

type RefreshActivity struct {
	Application string `json:"application"`
	ActivityFailure
}

type Resource added in v0.0.25

type Resource struct {
	Kubernetes struct {
		Namespace         string   `json:"namespace,omitempty" yaml:"namespace,omitempty"`
		Namespaces        []string `json:"namespaces,omitempty" yaml:"namespaces,omitempty"`
		NamespaceSelector string   `json:"namespaceSelector,omitempty" yaml:"namespaceSelector,omitempty"`
		Types             []string `json:"types,omitempty" yaml:"types,omitempty"`
		Selector          string   `json:"selector,omitempty" yaml:"selector,omitempty"`
	} `json:"kubernetes" yaml:"kubernetes"`
}

type ResourceList added in v0.0.25

type ResourceList struct {
	CPU    *api.NumberOrString `json:"cpu,omitempty"`
	Memory *api.NumberOrString `json:"memory,omitempty"`
}

func (*ResourceList) Get added in v0.1.2

func (rl *ResourceList) Get(name string) *api.NumberOrString

func (*ResourceList) Set added in v0.1.2

func (rl *ResourceList) Set(name string, value api.NumberOrString)

type RunActivity

type RunActivity struct {
	Scenario string `json:"scenario"`
	ActivityFailure
}

type ScanActivity

type ScanActivity struct {
	Scenario string `json:"scenario"`
	ActivityFailure
}

type Scenario

type Scenario struct {
	api.Metadata  `json:"-"`
	Name          ScenarioName  `json:"name,omitempty"`
	DisplayName   string        `json:"title,omitempty"`
	Configuration []interface{} `json:"configuration,omitempty"`
	Objective     []interface{} `json:"objective,omitempty"`
	Clusters      []string      `json:"clusters,omitempty"`

	StormForgePerformance interface{} `json:"stormforgePerf,omitempty"`
	Locust                interface{} `json:"locust,omitempty"`
	Custom                interface{} `json:"custom,omitempty"`
}

type ScenarioItem

type ScenarioItem struct {
	Scenario
}

func (*ScenarioItem) UnmarshalJSON

func (l *ScenarioItem) UnmarshalJSON(b []byte) error

type ScenarioList

type ScenarioList struct {
	// The scenario list metadata.
	api.Metadata `json:"-"`
	// The total number of items in the collection.
	TotalCount int `json:"totalCount,omitempty"`
	// The list of scenarios.
	Scenarios []ScenarioItem `json:"scenarios,omitempty"`
}

type ScenarioListQuery

type ScenarioListQuery struct{ api.IndexQuery }

type ScenarioName added in v0.0.25

type ScenarioName string

ScenarioName represents a name token used to identify a scenario.

func (ScenarioName) String added in v0.0.25

func (n ScenarioName) String() string

type Subscriber

type Subscriber interface {
	// Subscribe initiates a subscription that continues for the lifetime of the context.
	Subscribe(ctx context.Context, ch chan<- ActivityItem) error
}

Subscriber describes a strategy for subscribing to feed notifications.

type TargetRef added in v0.0.23

type TargetRef struct {
	Kind      string `json:"kind,omitempty"`
	Namespace string `json:"namespace,omitempty"`
	Workload  string `json:"workload,omitempty"`
}

type Template

type Template struct {
	// The list of parameters for this template.
	Parameters []TemplateParameter `json:"parameters,omitempty"`
	// The list of metrics for this template.
	Metrics []TemplateMetric `json:"metrics,omitempty"`
}

type TemplateMetric

type TemplateMetric struct {
	// The name of the metric.
	Name string `json:"name"`
	// The flag indicating this metric should be minimized.
	Minimize bool `json:"minimize,omitempty"`
	// The flag indicating this metric is optimized (nil defaults to true).
	Optimize *bool `json:"optimize,omitempty"`
	// The domain of the metric
	Bounds *TemplateMetricBounds `json:"bounds,omitempty"`
}

type TemplateMetricBounds

type TemplateMetricBounds struct {
	// The minimum value for a metric.
	Min float64 `json:"min,omitempty"`
	// The maximum value for a metric.
	Max float64 `json:"max,omitempty"`
}

type TemplateParameter

type TemplateParameter struct {
	// The name of the parameter.
	Name string `json:"name"`
	// The type of the parameter.
	Type string `json:"type"`
	// The optional baseline value of the parameter, either numeric or categorical.
	Baseline *api.NumberOrString `json:"baseline,omitempty"`
	// The domain of the parameter.
	Bounds *TemplateParameterBounds `json:"bounds,omitempty"`
	// The list of allowed categorical values for the parameter.
	Values []string `json:"values,omitempty"`
}

type TemplateParameterBounds

type TemplateParameterBounds struct {
	// The minimum value for a numeric parameter.
	Min json.Number `json:"min,omitempty"`
	// The maximum value for a numeric parameter.
	Max json.Number `json:"max,omitempty"`
}

type Tolerance added in v0.1.2

type Tolerance api.NumberOrString

func ToleranceFrom added in v0.1.2

func ToleranceFrom(s string) Tolerance

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL