bindata

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Nov 21, 2022 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Index

Constants

View Source
const (
	SelinuxDropDirectory = "/etc/selinux.d"
	SelinuxdPrivateDir   = "/var/run/selinuxd"
	SelinuxdSocketPath   = SelinuxdPrivateDir + "/selinuxd.sock"
	SelinuxdDBPath       = SelinuxdPrivateDir + "/selinuxd.db"
	MetricsImage         = "quay.io/brancz/kube-rbac-proxy:v0.13.1"

	InitContainerIDNonRootenabler              = 0
	InitContainerIDSelinuxSharedPoliciesCopier = 1
	ContainerIDDaemon                          = 0
	ContainerIDSelinuxd                        = 1
	ContainerIDLogEnricher                     = 2
	ContainerIDBpfRecorder                     = 3
	ContainerIDMetrics                         = 4
	DefaultHostProcPath                        = "/proc"
	MetricsContainerName                       = "metrics"
	LocalSeccompProfilePath                    = "security-profiles-operator.json"
)

Variables

View Source
var DefaultSPOD = &spodv1alpha1.SecurityProfilesOperatorDaemon{
	ObjectMeta: metav1.ObjectMeta{
		Name:   config.SPOdName,
		Labels: map[string]string{"app": config.OperatorName},
	},
	Spec: spodv1alpha1.SPODSpec{
		Verbosity:           0,
		EnableProfiling:     false,
		EnableSelinux:       nil,
		EnableLogEnricher:   false,
		EnableBpfRecorder:   false,
		StaticWebhookConfig: false,
		HostProcVolumePath:  DefaultHostProcPath,
		SelinuxOpts: spodv1alpha1.SelinuxOptions{
			AllowedSystemProfiles: []string{
				"container",
			},
		},
		Tolerations: []corev1.Toleration{
			{
				Key:      "node-role.kubernetes.io/master",
				Operator: corev1.TolerationOpExists,
				Effect:   corev1.TaintEffectNoSchedule,
			},
			{
				Key:      "node-role.kubernetes.io/control-plane",
				Operator: corev1.TolerationOpExists,
				Effect:   corev1.TaintEffectNoSchedule,
			},
			{
				Key:      "node.kubernetes.io/not-ready",
				Operator: corev1.TolerationOpExists,
				Effect:   corev1.TaintEffectNoExecute,
			},
		},
	},
}
View Source
var Manifest = &appsv1.DaemonSet{
	ObjectMeta: metav1.ObjectMeta{
		Name:      config.OperatorName,
		Namespace: config.OperatorName,
	},
	Spec: appsv1.DaemonSetSpec{
		UpdateStrategy: appsv1.DaemonSetUpdateStrategy{
			Type: appsv1.RollingUpdateDaemonSetStrategyType,
			RollingUpdate: &appsv1.RollingUpdateDaemonSet{

				MaxUnavailable: &intstr.IntOrString{Type: intstr.String, StrVal: "100%"},
			},
		},
		Selector: &metav1.LabelSelector{
			MatchLabels: map[string]string{
				"app":  config.OperatorName,
				"name": config.SPOdName,
			},
		},
		Template: corev1.PodTemplateSpec{
			ObjectMeta: metav1.ObjectMeta{
				Annotations: map[string]string{
					"openshift.io/scc": "privileged",
				},
				Labels: map[string]string{
					"app":  config.OperatorName,
					"name": config.SPOdName,
				},
			},
			Spec: corev1.PodSpec{
				ServiceAccountName: config.SPOdServiceAccount,
				SecurityContext: &corev1.PodSecurityContext{
					SeccompProfile: &corev1.SeccompProfile{
						Type: corev1.SeccompProfileTypeRuntimeDefault,
					},
				},
				InitContainers: []corev1.Container{
					{
						Name:            "non-root-enabler",
						Args:            []string{"non-root-enabler"},
						ImagePullPolicy: corev1.PullAlways,
						VolumeMounts: []corev1.VolumeMount{
							{
								Name:      "host-varlib-volume",
								MountPath: "/var/lib",
							},
							{
								Name:      "operator-profiles-volume",
								MountPath: "/opt/spo-profiles",
								ReadOnly:  true,
							},
							{
								Name:      "metrics-cert-volume",
								MountPath: metricsCertPath,
							},
						},
						SecurityContext: &corev1.SecurityContext{
							AllowPrivilegeEscalation: &falsely,
							ReadOnlyRootFilesystem:   &truly,
							Capabilities: &corev1.Capabilities{
								Drop: []corev1.Capability{"ALL"},
								Add:  []corev1.Capability{"CHOWN", "FOWNER", "FSETID", "DAC_OVERRIDE"},
							},
							RunAsUser: &userRoot,
							SELinuxOptions: &corev1.SELinuxOptions{

								Type: "spc_t",
							},
						},
						Resources: corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("32Mi"),
								corev1.ResourceCPU:              resource.MustParse("100m"),
								corev1.ResourceEphemeralStorage: resource.MustParse("10Mi"),
							},
							Limits: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("64Mi"),
								corev1.ResourceEphemeralStorage: resource.MustParse("50Mi"),
							},
						},
					},
					{
						Name:  "selinux-shared-policies-copier",
						Image: "quay.io/security-profiles-operator/selinuxd",

						Command: []string{"bash", "-c"},
						Args: []string{
							`set -x
chown 65535:0 /etc/selinux.d
chmod 750 /etc/selinux.d
semodule -i /usr/share/selinuxd/templates/*.cil
semodule -i /opt/spo-profiles/selinuxd.cil
semodule -i /opt/spo-profiles/selinuxrecording.cil
`,
						},
						VolumeMounts: []corev1.VolumeMount{
							{
								Name:      "selinux-drop-dir",
								MountPath: SelinuxDropDirectory,
							},
							{
								Name:      "operator-profiles-volume",
								MountPath: "/opt/spo-profiles",
								ReadOnly:  true,
							},
							{
								Name:      "host-fsselinux-volume",
								MountPath: "/sys/fs/selinux",
							},
							{
								Name:      "host-etcselinux-volume",
								MountPath: "/etc/selinux",
							},
							{
								Name:      "host-varlibselinux-volume",
								MountPath: "/var/lib/selinux",
							},
						},
						SecurityContext: &corev1.SecurityContext{
							AllowPrivilegeEscalation: &falsely,
							ReadOnlyRootFilesystem:   &truly,
							Capabilities: &corev1.Capabilities{
								Drop: []corev1.Capability{"ALL"},
								Add:  []corev1.Capability{"CHOWN", "FOWNER", "FSETID", "DAC_OVERRIDE"},
							},
							RunAsUser: &userRoot,
							SELinuxOptions: &corev1.SELinuxOptions{

								Type: "spc_t",
							},
						},
						Resources: corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("32Mi"),
								corev1.ResourceCPU:              resource.MustParse("100m"),
								corev1.ResourceEphemeralStorage: resource.MustParse("10Mi"),
							},
							Limits: corev1.ResourceList{

								corev1.ResourceMemory:           resource.MustParse("1024Mi"),
								corev1.ResourceEphemeralStorage: resource.MustParse("50Mi"),
							},
						},
					},
				},
				Containers: []corev1.Container{
					{
						Name:            config.OperatorName,
						Args:            []string{"daemon"},
						ImagePullPolicy: corev1.PullAlways,
						VolumeMounts: []corev1.VolumeMount{
							{
								Name:      "host-operator-volume",
								MountPath: config.ProfilesRootPath,
							},
							{
								Name:      "selinux-drop-dir",
								MountPath: SelinuxDropDirectory,
							},
							{
								Name:      "selinuxd-private-volume",
								MountPath: SelinuxdPrivateDir,
							},
							{
								Name:      "profile-recording-output-volume",
								MountPath: config.ProfileRecordingOutputPath,
							},
							{
								Name:      "grpc-server-volume",
								MountPath: filepath.Dir(config.GRPCServerSocketMetrics),
							},
						},
						SecurityContext: &corev1.SecurityContext{
							AllowPrivilegeEscalation: &falsely,
							ReadOnlyRootFilesystem:   &truly,
							Capabilities: &corev1.Capabilities{
								Drop: []corev1.Capability{"ALL"},
							},
							RunAsUser:  &userRootless,
							RunAsGroup: &userRootless,
							SELinuxOptions: &corev1.SELinuxOptions{

								Type: "spc_t",
							},
							SeccompProfile: &corev1.SeccompProfile{
								Type:             corev1.SeccompProfileTypeLocalhost,
								LocalhostProfile: &localSeccompProfilePath,
							},
						},
						Resources: corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("64Mi"),
								corev1.ResourceCPU:              resource.MustParse("100m"),
								corev1.ResourceEphemeralStorage: resource.MustParse("50Mi"),
							},
							Limits: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("128Mi"),
								corev1.ResourceEphemeralStorage: resource.MustParse("200Mi"),
							},
						},
						Env: []corev1.EnvVar{
							{
								Name: config.NodeNameEnvKey,
								ValueFrom: &corev1.EnvVarSource{
									FieldRef: &corev1.ObjectFieldSelector{
										FieldPath: "spec.nodeName",
									},
								},
							},
							{
								Name: "OPERATOR_NAMESPACE",
								ValueFrom: &corev1.EnvVarSource{
									FieldRef: &corev1.ObjectFieldSelector{
										FieldPath: "metadata.namespace",
									},
								},
							},
							{

								Name:  config.SPOdNameEnvKey,
								Value: config.SPOdName,
							},
						},
						Ports: []corev1.ContainerPort{
							{
								Name:          "liveness-port",
								ContainerPort: config.HealthProbePort,
								Protocol:      corev1.ProtocolTCP,
							},
						},
						StartupProbe: &corev1.Probe{
							ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{
								Path:   healthzPath,
								Port:   intstr.FromString("liveness-port"),
								Scheme: corev1.URISchemeHTTP,
							}},
							FailureThreshold: 10,
							PeriodSeconds:    3,
							TimeoutSeconds:   1,
							SuccessThreshold: 1,
						},
						LivenessProbe: &corev1.Probe{
							ProbeHandler: corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{
								Path:   healthzPath,
								Port:   intstr.FromString("liveness-port"),
								Scheme: corev1.URISchemeHTTP,
							}},
							FailureThreshold: 1,
							PeriodSeconds:    10,
							TimeoutSeconds:   1,
							SuccessThreshold: 1,
						},
					},
					{
						Name:  "selinuxd",
						Image: "quay.io/security-profiles-operator/selinuxd",
						Args: []string{
							"daemon",
							"--datastore-path", SelinuxdDBPath,
							"--socket-path", SelinuxdSocketPath,
							"--socket-uid", "0",
							"--socket-gid", "65535",
						},
						ImagePullPolicy: corev1.PullAlways,
						VolumeMounts: []corev1.VolumeMount{
							{
								Name:      "selinux-drop-dir",
								MountPath: SelinuxDropDirectory,
								ReadOnly:  true,
							},
							{
								Name:      "selinuxd-private-volume",
								MountPath: SelinuxdPrivateDir,
							},
							{
								Name:      "host-fsselinux-volume",
								MountPath: "/sys/fs/selinux",
							},
							{
								Name:      "host-etcselinux-volume",
								MountPath: "/etc/selinux",
							},
							{
								Name:      "host-varlibselinux-volume",
								MountPath: "/var/lib/selinux",
							},
						},
						SecurityContext: &corev1.SecurityContext{
							ReadOnlyRootFilesystem: &truly,
							RunAsUser:              &userRoot,
							RunAsGroup:             &userRoot,
							Capabilities: &corev1.Capabilities{
								Add: []corev1.Capability{"CHOWN", "FOWNER", "FSETID", "DAC_OVERRIDE"},
							},
							SELinuxOptions: &corev1.SELinuxOptions{
								Type: "selinuxd.process",
							},
						},
						Resources: corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("512Mi"),
								corev1.ResourceCPU:              resource.MustParse("100m"),
								corev1.ResourceEphemeralStorage: resource.MustParse("200Mi"),
							},
							Limits: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("1024Mi"),
								corev1.ResourceEphemeralStorage: resource.MustParse("400Mi"),
							},
						},
					},
					{
						Name:            "log-enricher",
						Args:            []string{"log-enricher"},
						ImagePullPolicy: corev1.PullAlways,
						VolumeMounts: []corev1.VolumeMount{
							{
								Name:      "host-auditlog-volume",
								MountPath: filepath.Dir(config.AuditLogPath),
								ReadOnly:  true,
							},
							{
								Name:      "host-syslog-volume",
								MountPath: filepath.Dir(config.SyslogLogPath),
								ReadOnly:  true,
							},
							{
								Name:      "grpc-server-volume",
								MountPath: filepath.Dir(config.GRPCServerSocketEnricher),
							},
						},
						SecurityContext: &corev1.SecurityContext{
							ReadOnlyRootFilesystem: &truly,
							Privileged:             &truly,
							RunAsUser:              &userRoot,
							RunAsGroup:             &userRoot,
							SELinuxOptions: &corev1.SELinuxOptions{

								Type: "spc_t",
							},
						},
						Resources: corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("64Mi"),
								corev1.ResourceCPU:              resource.MustParse("50m"),
								corev1.ResourceEphemeralStorage: resource.MustParse("10Mi"),
							},
							Limits: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("256Mi"),
								corev1.ResourceEphemeralStorage: resource.MustParse("128Mi"),
							},
						},
						Env: []corev1.EnvVar{
							{
								Name: config.NodeNameEnvKey,
								ValueFrom: &corev1.EnvVarSource{
									FieldRef: &corev1.ObjectFieldSelector{
										FieldPath: "spec.nodeName",
									},
								},
							},
						},
					},
					{
						Name:            "bpf-recorder",
						Args:            []string{"bpf-recorder"},
						ImagePullPolicy: corev1.PullAlways,
						VolumeMounts: []corev1.VolumeMount{
							{
								Name:      "sys-kernel-debug-volume",
								MountPath: sysKernelDebugPath,
								ReadOnly:  true,
							},
							{
								Name:      "host-etc-osrelease-volume",
								MountPath: etcOSReleasePath,
							},
							{
								Name:      "tmp-volume",
								MountPath: "/tmp",
							},
							{
								Name:      "grpc-server-volume",
								MountPath: filepath.Dir(config.GRPCServerSocketBpfRecorder),
							},
						},
						SecurityContext: &corev1.SecurityContext{
							ReadOnlyRootFilesystem: &truly,
							Privileged:             &truly,
							RunAsUser:              &userRoot,
							RunAsGroup:             &userRoot,
							SELinuxOptions: &corev1.SELinuxOptions{

								Type: "spc_t",
							},
						},
						Resources: corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("64Mi"),
								corev1.ResourceCPU:              resource.MustParse("50m"),
								corev1.ResourceEphemeralStorage: resource.MustParse("10Mi"),
							},
							Limits: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("128Mi"),
								corev1.ResourceEphemeralStorage: resource.MustParse("20Mi"),
							},
						},
						Env: []corev1.EnvVar{
							{
								Name: config.NodeNameEnvKey,
								ValueFrom: &corev1.EnvVarSource{
									FieldRef: &corev1.ObjectFieldSelector{
										FieldPath: "spec.nodeName",
									},
								},
							},
						},
					},
					{
						Name:            MetricsContainerName,
						Image:           MetricsImage,
						ImagePullPolicy: corev1.PullIfNotPresent,
						Args: []string{
							fmt.Sprintf("--secure-listen-address=0.0.0.0:%d", metricsPort),
							"--upstream=http://127.0.0.1:8080",
							"--v=10",
							fmt.Sprintf("--tls-cert-file=%s", filepath.Join(metricsCertPath, "tls.crt")),
							fmt.Sprintf("--tls-private-key-file=%s", filepath.Join(metricsCertPath, "tls.key")),
						},
						Resources: corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("32Mi"),
								corev1.ResourceCPU:              resource.MustParse("50m"),
								corev1.ResourceEphemeralStorage: resource.MustParse("10Mi"),
							},
							Limits: corev1.ResourceList{
								corev1.ResourceMemory:           resource.MustParse("128Mi"),
								corev1.ResourceEphemeralStorage: resource.MustParse("20Mi"),
							},
						},
						SecurityContext: &corev1.SecurityContext{
							AllowPrivilegeEscalation: &falsely,
							ReadOnlyRootFilesystem:   &truly,
						},
						Ports: []corev1.ContainerPort{
							{Name: "https", ContainerPort: metricsPort},
						},
						VolumeMounts: []corev1.VolumeMount{
							{
								Name:      "metrics-cert-volume",
								MountPath: metricsCertPath,
								ReadOnly:  true,
							},
						},
					},
				},
				Volumes: []corev1.Volume{

					{
						Name: "host-varlib-volume",
						VolumeSource: corev1.VolumeSource{
							HostPath: &corev1.HostPathVolumeSource{
								Path: "/var/lib",
								Type: &hostPathDirectory,
							},
						},
					},
					{
						Name: "host-operator-volume",
						VolumeSource: corev1.VolumeSource{
							HostPath: &corev1.HostPathVolumeSource{
								Path: "/var/lib/security-profiles-operator",
								Type: &hostPathDirectoryOrCreate,
							},
						},
					},
					{
						Name: "operator-profiles-volume",
						VolumeSource: corev1.VolumeSource{
							ConfigMap: &corev1.ConfigMapVolumeSource{
								LocalObjectReference: corev1.LocalObjectReference{
									Name: "security-profiles-operator-profile",
								},
							},
						},
					},
					{
						Name: "selinux-drop-dir",
						VolumeSource: corev1.VolumeSource{
							EmptyDir: &corev1.EmptyDirVolumeSource{},
						},
					},
					{
						Name: "selinuxd-private-volume",
						VolumeSource: corev1.VolumeSource{
							EmptyDir: &corev1.EmptyDirVolumeSource{},
						},
					},

					{
						Name: "host-fsselinux-volume",
						VolumeSource: corev1.VolumeSource{
							HostPath: &corev1.HostPathVolumeSource{
								Path: "/sys/fs/selinux",
								Type: &hostPathDirectory,
							},
						},
					},
					{
						Name: "host-etcselinux-volume",
						VolumeSource: corev1.VolumeSource{
							HostPath: &corev1.HostPathVolumeSource{
								Path: "/etc/selinux",
								Type: &hostPathDirectory,
							},
						},
					},
					{
						Name: "host-varlibselinux-volume",
						VolumeSource: corev1.VolumeSource{
							HostPath: &corev1.HostPathVolumeSource{
								Path: "/var/lib/selinux",
								Type: &hostPathDirectory,
							},
						},
					},
					{
						Name: "profile-recording-output-volume",
						VolumeSource: corev1.VolumeSource{
							HostPath: &corev1.HostPathVolumeSource{
								Path: config.ProfileRecordingOutputPath,
								Type: &hostPathDirectoryOrCreate,
							},
						},
					},
					{
						Name: "host-auditlog-volume",
						VolumeSource: corev1.VolumeSource{
							HostPath: &corev1.HostPathVolumeSource{
								Path: filepath.Dir(config.AuditLogPath),
								Type: &hostPathDirectoryOrCreate,
							},
						},
					},
					{
						Name: "host-syslog-volume",
						VolumeSource: corev1.VolumeSource{
							HostPath: &corev1.HostPathVolumeSource{
								Path: filepath.Dir(config.SyslogLogPath),
								Type: &hostPathDirectoryOrCreate,
							},
						},
					},
					{
						Name: "metrics-cert-volume",
						VolumeSource: corev1.VolumeSource{
							Secret: &corev1.SecretVolumeSource{
								SecretName: metricsServerCert,
							},
						},
					},
					{
						Name: "sys-kernel-debug-volume",
						VolumeSource: corev1.VolumeSource{
							HostPath: &corev1.HostPathVolumeSource{
								Path: sysKernelDebugPath,
								Type: &hostPathDirectory,
							},
						},
					},
					{
						Name: "host-etc-osrelease-volume",
						VolumeSource: corev1.VolumeSource{
							HostPath: &corev1.HostPathVolumeSource{
								Path: etcOSReleasePath,
								Type: &hostPathFile,
							},
						},
					},
					{
						Name: "tmp-volume",
						VolumeSource: corev1.VolumeSource{
							EmptyDir: &corev1.EmptyDirVolumeSource{},
						},
					},
					{
						Name: "grpc-server-volume",
						VolumeSource: corev1.VolumeSource{
							EmptyDir: &corev1.EmptyDirVolumeSource{},
						},
					},
				},
				Tolerations: []corev1.Toleration{
					{
						Effect: corev1.TaintEffectNoSchedule,
						Key:    "node-role.kubernetes.io/master",
					},
					{
						Effect: corev1.TaintEffectNoSchedule,
						Key:    "node-role.kubernetes.io/control-plane",
					},
					{
						Effect:   corev1.TaintEffectNoExecute,
						Key:      "node.kubernetes.io/not-ready",
						Operator: corev1.TolerationOpExists,
					},
				},
				NodeSelector: map[string]string{
					"kubernetes.io/os": "linux",
				},
			},
		},
	},
}

Functions

func CustomHostProcVolume added in v0.4.1

func CustomHostProcVolume(path string) (corev1.Volume, corev1.VolumeMount)

CustomHostProcVolume returns a new host /proc path volume as well as corresponding mount used for the log-enricher or bpf-recorder.

func DefaultProfiles

func DefaultProfiles() []*seccompprofileapi.SeccompProfile

DefaultProfiles returns the default profiles deployed by the operator.

func GetMetricsService added in v0.4.2

func GetMetricsService(
	namespace string,
	caInjectType CAInjectType,
) *corev1.Service

func ServiceMonitor added in v0.4.0

func ServiceMonitor() *v1.ServiceMonitor

ServiceMonitor returns the default ServiceMonitor for automatic metrics retrieval via the prometheus operator.

Types

type CAInjectType added in v0.4.2

type CAInjectType uint
const (
	// CAInjectTypeCertManager can be used to rely on Cert Manager for CA
	// injection.
	CAInjectTypeCertManager CAInjectType = 0

	// CAInjectTypeOpenShift can be used in OpenShift environments.
	CAInjectTypeOpenShift CAInjectType = 1
)

func GetCAInjectType added in v0.4.2

func GetCAInjectType(
	ctx context.Context, log logr.Logger, namespace string, c client.Client,
) (res CAInjectType, err error)

type CertManagerResources added in v0.4.2

type CertManagerResources struct {
	// contains filtered or unexported fields
}

func GetCertManagerResources added in v0.4.2

func GetCertManagerResources(namespace string) *CertManagerResources

func (*CertManagerResources) Create added in v0.4.2

func (*CertManagerResources) Update added in v0.4.2

type Webhook added in v0.4.2

type Webhook struct {
	// contains filtered or unexported fields
}

func GetWebhook added in v0.4.2

func GetWebhook(
	log logr.Logger,
	namespace string,
	webhookOpts []spodv1alpha1.WebhookOptions,
	image string,
	pullPolicy corev1.PullPolicy,
	caInjectType CAInjectType,
	tolerations []corev1.Toleration,
	imagePullSecrets []corev1.LocalObjectReference,
) *Webhook

func (*Webhook) Create added in v0.4.2

func (w *Webhook) Create(ctx context.Context, c client.Client) error

func (*Webhook) NeedsUpdate added in v0.4.3

func (w *Webhook) NeedsUpdate(ctx context.Context, c client.Client) (bool, error)

func (*Webhook) Update added in v0.4.2

func (w *Webhook) Update(ctx context.Context, c client.Client) error

Jump to

Keyboard shortcuts

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