diff --git a/Makefile b/Makefile index b29fe5c31..2024961b5 100644 --- a/Makefile +++ b/Makefile @@ -20,8 +20,8 @@ GOPROXY ?= $(shell go env GOPROXY) # Runnable tools GO ?= go -BUF := $(GO) run github.com/bufbuild/buf/cmd/buf@v1.11 -CONTROLLER_GEN := $(GO) run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.11 +BUF := $(GO) run github.com/bufbuild/buf/cmd/buf@v1.29 +CONTROLLER_GEN := $(GO) run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14 GOFUMPT := $(GO) run mvdan.cc/gofumpt@v0.4 KUSTOMIZE := $(GO) run sigs.k8s.io/kustomize/kustomize/v4@v4.5 SETUP_ENVTEST := $(GO) run sigs.k8s.io/controller-runtime/tools/setup-envtest@v0.0.0-20220304125252-9ee63fc65a97 @@ -39,7 +39,7 @@ help: ## Print this help @echo @echo Individual binaries can be built with their name. For example, \`make tink-server\`. @echo - @echo Individual images can be built with their name appended with -image. For example, + @echo Individual images can be built with their name appended with -image. For example, @echo \`make tink-server-image\`. # Version defines the string injected into binaries that indicates the version of the build. @@ -49,21 +49,21 @@ help: ## Print this help VERSION ?= $(shell git rev-parse --short HEAD) # Define all the binaries we build for this project that get packaged into containers. -BINARIES := tink-server tink-agent tink-worker tink-controller virtual-worker +BINARIES := tink-server tink-agent tink-worker tink-controller tink-controller-v1alpha2 virtual-worker .PHONY: build build: $(BINARIES) ## Build all tink binaries. Cross build by setting GOOS and GOARCH. # Create targets for all the binaries we build. They can be individually invoked with `make `. -# For example, `make tink-server`. Callers can cross build by defining the GOOS and GOARCH +# For example, `make tink-server`. Callers can cross build by defining the GOOS and GOARCH # variables. For example, `GOOS=linux GOARCH=arm64 make tink-server`. # See https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html. .PHONY: $(BINARIES) $(BINARIES): CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GO) build $(LDFLAGS) -o ./bin/$@-$(GOOS)-$(GOARCH) ./cmd/$@ -# IMAGE_ARGS is resolved when its used in the `%-image` targets. Consequently, the $* automatic -# variable isn't evaluated until the target is called. +# IMAGE_ARGS is resolved when its used in the `%-image` targets. Consequently, the $* automatic +# variable isn't evaluated until the target is called. # See https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html. IMAGE_ARGS ?= -t $* @@ -77,8 +77,8 @@ images: $(addsuffix -image,$(BINARIES)) ## Build all tink container images. All # We only build Linux images so we need to force binaries to be built for Linux. Exporting the # GOOS variable ensures the recipe's binary dependency is built for Linux. # -# The $$* leverages .SECONDEXPANSION to specify the matched part of the target name as a -# dependency. In doing so, we ensure the binary is built so it can be copied into the image. For +# The $$* leverages .SECONDEXPANSION to specify the matched part of the target name as a +# dependency. In doing so, we ensure the binary is built so it can be copied into the image. For # example, `make tink-server-image` will depend on `tink-server`. # See https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html. # See https://www.gnu.org/software/make/manual/html_node/Secondary-Expansion.html. @@ -109,7 +109,8 @@ generate-proto: buf.gen.yaml buf.lock $(shell git ls-files '**/*.proto') _protoc $(GOFUMPT) -w internal/proto/workflow/v2/*.pb.* .PHONY: generate -generate: generate-proto generate-go generate-manifests ## Generate code, manifests etc. +generate: ## Generate code, manifests etc. +generate: generate-proto generate-go generate-manifests .PHONY: generate-go generate-go: @@ -117,29 +118,32 @@ generate-go: $(GOFUMPT) -w ./api .PHONY: generate-manifests -generate-manifests: generate-crds generate-rbacs generate-server-rbacs ## Generate manifests e.g. CRD, RBAC etc. +generate-manifests: ## Generate manifests e.g. CRD, RBAC etc. +generate-manifests: generate-crds generate-rbac .PHONY: generate-crds generate-crds: $(CONTROLLER_GEN) \ paths=./api/... \ crd:crdVersions=v1 \ - rbac:roleName=manager-role \ output:crd:dir=./config/crd/bases \ output:webhook:dir=./config/webhook \ webhook $(YAMLFMT) ./config/crd/bases/* ./config/webhook/* -.PHONY: generate-rbacs -generate-rbacs: +.PHONY: +generate-rbac: generate-manager-rbac generate-server-rbac + +.PHONY: generate-controller-rbac +generate-manager-rbac: $(CONTROLLER_GEN) \ - paths=./internal/controller/... \ - output:rbac:dir=./config/rbac/ \ + paths=./internal/workflow/... \ + output:rbac:dir=./config/manager-rbac/ \ rbac:roleName=manager-role $(YAMLFMT) ./config/rbac/* -.PHONY: generate-server-rbacs -generate-server-rbacs: +.PHONY: generate-server-rbac +generate-server-rbac: $(CONTROLLER_GEN) \ paths=./internal/server/... \ output:rbac:dir=./config/server-rbac \ diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c9c520e1b..ad563c10b 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* Copyright The Tinkerbell Authors. diff --git a/api/v1alpha2/conditions.go b/api/v1alpha2/conditions.go index e1e1e1b75..089635b00 100644 --- a/api/v1alpha2/conditions.go +++ b/api/v1alpha2/conditions.go @@ -30,7 +30,7 @@ type Condition struct { Status ConditionStatus `json:"status"` // LastTransition is the last time the condition transitioned from one status to another. - LastTransition *metav1.Time `json:"lastTransitionTime"` + LastTransition metav1.Time `json:"lastTransitionTime"` // Reason is a short CamelCase description for the conditions last transition. // +optional diff --git a/api/v1alpha2/workflow.go b/api/v1alpha2/workflow.go index fbfe67828..aa63f7dcf 100644 --- a/api/v1alpha2/workflow.go +++ b/api/v1alpha2/workflow.go @@ -32,15 +32,17 @@ type WorkflowStatus struct { // StartedAt is the time the first action was requested. Nil indicates the Workflow has not // started. + // +optional StartedAt *metav1.Time `json:"startedAt,omitempty"` // LastTransition is the observed time when State transitioned last. - LastTransition *metav1.Time `json:"lastTransitioned,omitempty"` + LastTransition metav1.Time `json:"lastTransitioned,omitempty"` // State describes the current state of the Workflow. State WorkflowState `json:"state,omitempty"` // Conditions details a set of observations about the Workflow. + // +optional Conditions Conditions `json:"conditions"` } diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go index 12fcb75cc..25915eec3 100644 --- a/api/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* Copyright The Tinkerbell Authors. @@ -95,10 +94,7 @@ func (in *ActionStatus) DeepCopy() *ActionStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in - if in.LastTransition != nil { - in, out := &in.LastTransition, &out.LastTransition - *out = (*in).DeepCopy() - } + in.LastTransition.DeepCopyInto(&out.LastTransition) if in.Reason != nil { in, out := &in.Reason, &out.Reason *out = new(string) @@ -671,10 +667,7 @@ func (in *WorkflowStatus) DeepCopyInto(out *WorkflowStatus) { in, out := &in.StartedAt, &out.StartedAt *out = (*in).DeepCopy() } - if in.LastTransition != nil { - in, out := &in.LastTransition, &out.LastTransition - *out = (*in).DeepCopy() - } + in.LastTransition.DeepCopyInto(&out.LastTransition) if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make(Conditions, len(*in)) diff --git a/buf.lock b/buf.lock index 7724da878..4144ecf50 100644 --- a/buf.lock +++ b/buf.lock @@ -5,3 +5,4 @@ deps: owner: googleapis repository: googleapis commit: a86849a25cc04f4dbe9b15ddddfbc488 + digest: shake256:e19143328f8cbfe13fc226aeee5e63773ca494693a72740a7560664270039a380d94a1344234b88c7691311460df9a9b1c2982190d0a2612eae80368718e1943 diff --git a/cmd/tink-controller-v1alpha2/Dockerfile b/cmd/tink-controller-v1alpha2/Dockerfile new file mode 100644 index 000000000..6a4b4cf6b --- /dev/null +++ b/cmd/tink-controller-v1alpha2/Dockerfile @@ -0,0 +1,10 @@ +FROM alpine:3.15 + +ARG TARGETOS +ARG TARGETARCH + +RUN apk add --no-cache --update --upgrade ca-certificates + +COPY bin/tink-controller-v1alpha2-${TARGETOS}-${TARGETARCH} /usr/bin/tink-controller + +ENTRYPOINT ["/usr/bin/tink-controller"] diff --git a/cmd/tink-controller-v1alpha2/main.go b/cmd/tink-controller-v1alpha2/main.go new file mode 100644 index 000000000..50b0c9238 --- /dev/null +++ b/cmd/tink-controller-v1alpha2/main.go @@ -0,0 +1,179 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/go-logr/logr" + "github.com/go-logr/zapr" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + tinkv1 "github.com/tinkerbell/tink/api/v1alpha2" + "github.com/tinkerbell/tink/internal/workflow" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/runtime" + amruntimeutil "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/metrics/server" +) + +// version is set at build time. +var version = "devel" + +// scheme is passed to the manager. +var scheme = runtime.NewScheme() + +func init() { + amruntimeutil.Must(tinkv1.AddToScheme(scheme)) + + //+kubebuilder:scaffold:scheme +} + +type Config struct { + K8sAPI string + Kubeconfig string // only applies to out of cluster + MetricsAddr string + ProbeAddr string + EnableLeaderElection bool +} + +func (c *Config) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&c.K8sAPI, "kubernetes", "", + "The Kubernetes API URL, used for in-cluster client construction.") + fs.StringVar(&c.Kubeconfig, "kubeconfig", "", "Absolute path to the kubeconfig file") + fs.StringVar(&c.MetricsAddr, "metrics-bind-address", ":8080", + "The address the metric endpoint binds to.") + fs.StringVar(&c.ProbeAddr, "health-probe-bind-address", ":8081", + "The address the probe endpoint binds to.") + fs.BoolVar(&c.EnableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") +} + +func main() { + cmd := NewRootCommand() + if err := cmd.Execute(); err != nil { + os.Exit(1) + } +} + +func NewRootCommand() *cobra.Command { + var config Config + + zlog, err := zap.NewProduction() + if err != nil { + panic(err) + } + logger := zapr.NewLogger(zlog).WithName("github.com/tinkerbell/tink") + + cmd := &cobra.Command{ + Use: "tink-controller", + PreRunE: func(cmd *cobra.Command, args []string) error { + viper, err := createViper(logger) + if err != nil { + return fmt.Errorf("config init: %w", err) + } + return applyViper(viper, cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + logger.Info("Starting controller version " + version) + + ccfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ExplicitPath: config.Kubeconfig}, + &clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: config.K8sAPI}}) + + cfg, err := ccfg.ClientConfig() + if err != nil { + return err + } + + namespace, _, err := ccfg.Namespace() + if err != nil { + return err + } + + opts := ctrl.Options{ + Logger: logger, + LeaderElection: config.EnableLeaderElection, + LeaderElectionID: "tink.tinkerbell.org", + LeaderElectionNamespace: namespace, + Metrics: server.Options{ + BindAddress: config.MetricsAddr, + }, + HealthProbeBindAddress: config.ProbeAddr, + Scheme: scheme, + } + + mgr, err := ctrl.NewManager(cfg, opts) + if err != nil { + return err + } + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + return err + } + + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + return err + } + + if err := workflow.NewReconciler(mgr.GetClient()).SetupWithManager(mgr); err != nil { + return err + } + + return mgr.Start(cmd.Context()) + }, + } + config.AddFlags(cmd.Flags()) + return cmd +} + +func createViper(logger logr.Logger) (*viper.Viper, error) { + v := viper.New() + v.AutomaticEnv() + v.SetConfigName("tink-controller") + v.AddConfigPath("/etc/tinkerbell") + v.AddConfigPath(".") + v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + + // If a config file is found, read it in. + if err := v.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + return nil, fmt.Errorf("loading config file: %w", err) + } + logger.Info("no config file found") + } else { + logger.Info("loaded config file", "configFile", v.ConfigFileUsed()) + } + + return v, nil +} + +func applyViper(v *viper.Viper, cmd *cobra.Command) error { + errors := []error{} + + cmd.Flags().VisitAll(func(f *pflag.Flag) { + if !f.Changed && v.IsSet(f.Name) { + val := v.Get(f.Name) + if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil { + errors = append(errors, err) + return + } + } + }) + + if len(errors) > 0 { + errs := []string{} + for _, err := range errors { + errs = append(errs, err.Error()) + } + return fmt.Errorf(strings.Join(errs, ", ")) + } + + return nil +} diff --git a/cmd/tink-controller/main.go b/cmd/tink-controller/main.go index 19ce5f47e..10726a13e 100644 --- a/cmd/tink-controller/main.go +++ b/cmd/tink-controller/main.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/tinkerbell/tink/internal/controller" + "github.com/tinkerbell/tink/internal/deprecated/controller" "go.uber.org/zap" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" diff --git a/config/crd/bases/tinkerbell.org_hardware.yaml b/config/crd/bases/tinkerbell.org_hardware.yaml index 760138e83..4749392a6 100644 --- a/config/crd/bases/tinkerbell.org_hardware.yaml +++ b/config/crd/bases/tinkerbell.org_hardware.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.14.0 name: hardware.tinkerbell.org spec: group: tinkerbell.org @@ -27,10 +27,19 @@ spec: description: Hardware is the Schema for the Hardware API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -38,10 +47,16 @@ spec: description: HardwareSpec defines the desired state of Hardware. properties: bmcRef: - description: BMCRef contains a relation to a BMC state management type in the same namespace as the Hardware. This may be used for BMC management by orchestrators. + description: |- + BMCRef contains a relation to a BMC state management type in the same + namespace as the Hardware. This may be used for BMC management by + orchestrators. properties: apiGroup: - description: APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group. For any other third-party types, APIGroup is required. + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. type: string kind: description: Kind is the type of resource being referenced @@ -338,16 +353,22 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: Resources represents known resources that are available on a machine. Resources may be used for scheduling by orchestrators. + description: |- + Resources represents known resources that are available on a machine. + Resources may be used for scheduling by orchestrators. type: object tinkVersion: format: int64 type: integer userData: - description: UserData is the user data to configure in the hardware's metadata + description: |- + UserData is the user data to configure in the hardware's + metadata type: string vendorData: - description: VendorData is the vendor data to configure in the hardware's metadata + description: |- + VendorData is the vendor data to configure in the hardware's + metadata type: string type: object status: @@ -373,10 +394,19 @@ spec: description: Hardware is a logical representation of a machine that can execute Workflows. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -386,7 +416,10 @@ spec: description: BMCRef references a Rufio Machine object. properties: name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object x-kubernetes-map-type: atomic @@ -401,7 +434,9 @@ spec: type: string type: object ipxe: - description: IPXE provides iPXE script override fields. This is useful for debugging or netboot customization. + description: |- + IPXE provides iPXE script override fields. This is useful for debugging or netboot + customization. properties: inline: description: Content is an inline iPXE script. @@ -411,7 +446,9 @@ spec: type: string type: object kernelParams: - description: KernelParams passed to the kernel when launching the OSIE. Parameters are joined with a space. + description: |- + KernelParams passed to the kernel when launching the OSIE. Parameters are joined with a + space. items: type: string type: array @@ -420,7 +457,9 @@ spec: description: NetworkInterface is the desired configuration for a particular network interface. properties: dhcp: - description: DHCP is the basic network information for serving DHCP requests. Required when DisbaleDHCP is false. + description: |- + DHCP is the basic network information for serving DHCP requests. Required when DisbaleDHCP + is false. properties: gateway: description: Gateway is the default gateway address to serve. @@ -435,7 +474,9 @@ spec: type: string leaseTimeSeconds: default: 86400 - description: LeaseTimeSeconds to serve. 24h default. Maximum equates to max uint32 as defined by RFC 2132 § 9.2 (https://www.rfc-editor.org/rfc/rfc2132.html#section-9.2). + description: |- + LeaseTimeSeconds to serve. 24h default. Maximum equates to max uint32 as defined by RFC 2132 + § 9.2 (https://www.rfc-editor.org/rfc/rfc2132.html#section-9.2). format: int64 maximum: 4294967295 minimum: 0 @@ -468,7 +509,9 @@ spec: type: boolean disableNetboot: default: false - description: DisableNetboot disables netbooting for this interface. The interface will still receive network information specified by DHCP. + description: |- + DisableNetboot disables netbooting for this interface. The interface will still receive + network information specified by DHCP. type: boolean type: object description: NetworkInterfaces defines the desired DHCP and netboot configuration for a network interface. @@ -477,14 +520,17 @@ spec: description: OSIE describes the Operating System Installation Environment to be netbooted. properties: name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object x-kubernetes-map-type: atomic storageDevices: description: StorageDevices is a list of storage devices that will be available in the OSIE. items: - description: "StorageDevice describes a storage device path that will be present in the OSIE. StorageDevices must be valid Linux paths. They should not contain partitions. \n Good \n /dev/sda /dev/nvme0n1 \n Bad (contains partitions) \n /dev/sda1 /dev/nvme0n1p1 \n Bad (invalid Linux path) \n \\dev\\sda" + description: "StorageDevice describes a storage device path that will be present in the OSIE.\nStorageDevices must be valid Linux paths. They should not contain partitions.\n\n\nGood\n\n\n\t/dev/sda\n\t/dev/nvme0n1\n\n\nBad (contains partitions)\n\n\n\t/dev/sda1\n\t/dev/nvme0n1p1\n\n\nBad (invalid Linux path)\n\n\n\t\\dev\\sda" pattern: ^(/[^/ ]*)+/?$ type: string type: array diff --git a/config/crd/bases/tinkerbell.org_osies.yaml b/config/crd/bases/tinkerbell.org_osies.yaml index 40f22b85d..2e994fbf9 100644 --- a/config/crd/bases/tinkerbell.org_osies.yaml +++ b/config/crd/bases/tinkerbell.org_osies.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.14.0 name: osies.tinkerbell.org spec: group: tinkerbell.org @@ -32,13 +32,24 @@ spec: - name: v1alpha2 schema: openAPIV3Schema: - description: OSIE describes an Operating System Installation Environment. It is used by Tinkerbell to provision machines and should launch the Tink Worker component. + description: |- + OSIE describes an Operating System Installation Environment. It is used by Tinkerbell + to provision machines and should launch the Tink Worker component. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object diff --git a/config/crd/bases/tinkerbell.org_templates.yaml b/config/crd/bases/tinkerbell.org_templates.yaml index 4d21fa013..ce90b438c 100644 --- a/config/crd/bases/tinkerbell.org_templates.yaml +++ b/config/crd/bases/tinkerbell.org_templates.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.14.0 name: templates.tinkerbell.org spec: group: tinkerbell.org @@ -27,10 +27,19 @@ spec: description: Template is the Schema for the Templates API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -55,30 +64,50 @@ spec: - name: v1alpha2 schema: openAPIV3Schema: - description: Template defines a set of actions to be run on a target machine. The template is rendered prior to execution where it is exposed to Hardware and user defined data. Most fields within the TemplateSpec may contain templates values excluding .TemplateSpec.Actions[].Name. See https://pkg.go.dev/text/template for more details. + description: |- + Template defines a set of actions to be run on a target machine. The template is rendered + prior to execution where it is exposed to Hardware and user defined data. Most fields within the + TemplateSpec may contain templates values excluding .TemplateSpec.Actions[].Name. + See https://pkg.go.dev/text/template for more details. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object spec: properties: actions: - description: Actions defines the set of actions to be run on a target machine. Actions are run sequentially in the order they are specified. At least 1 action must be specified. Names of actions must be unique within a Template. + description: |- + Actions defines the set of actions to be run on a target machine. Actions are run sequentially + in the order they are specified. At least 1 action must be specified. Names of actions + must be unique within a Template. items: description: Action defines an individual action to be run on a target machine. properties: args: - description: Args are a set of arguments to be passed to the command executed by the container on launch. + description: |- + Args are a set of arguments to be passed to the command executed by the container on + launch. items: type: string type: array cmd: - description: Cmd defines the command to use when launching the image. It overrides the default command of the action. It must be a unix path to an executable program. + description: |- + Cmd defines the command to use when launching the image. It overrides the default command + of the action. It must be a unix path to an executable program. pattern: ^(/[^/ ]*)+/?$ type: string env: @@ -105,7 +134,7 @@ spec: volumes: description: Volumes defines the volumes to mount into the container. items: - description: "Volume is a specification for mounting a volume in an action. Volumes take the form {SRC-VOLUME-NAME | SRC-HOST-DIR}:TGT-CONTAINER-DIR:OPTIONS. When specifying a VOLUME-NAME that does not exist it will be created for you. Examples: \n Read-only bind mount bound to /data \n /etc/data:/data:ro \n Writable volume name bound to /data \n shared_volume:/data \n See https://docs.docker.com/storage/volumes/ for additional details." + description: "Volume is a specification for mounting a volume in an action. Volumes take the form\n{SRC-VOLUME-NAME | SRC-HOST-DIR}:TGT-CONTAINER-DIR:OPTIONS. When specifying a VOLUME-NAME that\ndoes not exist it will be created for you. Examples:\n\n\nRead-only bind mount bound to /data\n\n\n\t/etc/data:/data:ro\n\n\nWritable volume name bound to /data\n\n\n\tshared_volume:/data\n\n\nSee https://docs.docker.com/storage/volumes/ for additional details." type: string type: array required: @@ -117,12 +146,16 @@ spec: env: additionalProperties: type: string - description: Env defines environment variables to be available in all actions. If an action specifies the same environment variable it will take precedence. + description: |- + Env defines environment variables to be available in all actions. If an action specifies + the same environment variable it will take precedence. type: object volumes: - description: Volumes to be mounted on all actions. If an action specifies the same volume it will take precedence. + description: |- + Volumes to be mounted on all actions. If an action specifies the same volume it will take + precedence. items: - description: "Volume is a specification for mounting a volume in an action. Volumes take the form {SRC-VOLUME-NAME | SRC-HOST-DIR}:TGT-CONTAINER-DIR:OPTIONS. When specifying a VOLUME-NAME that does not exist it will be created for you. Examples: \n Read-only bind mount bound to /data \n /etc/data:/data:ro \n Writable volume name bound to /data \n shared_volume:/data \n See https://docs.docker.com/storage/volumes/ for additional details." + description: "Volume is a specification for mounting a volume in an action. Volumes take the form\n{SRC-VOLUME-NAME | SRC-HOST-DIR}:TGT-CONTAINER-DIR:OPTIONS. When specifying a VOLUME-NAME that\ndoes not exist it will be created for you. Examples:\n\n\nRead-only bind mount bound to /data\n\n\n\t/etc/data:/data:ro\n\n\nWritable volume name bound to /data\n\n\n\tshared_volume:/data\n\n\nSee https://docs.docker.com/storage/volumes/ for additional details." type: string type: array type: object diff --git a/config/crd/bases/tinkerbell.org_workflows.yaml b/config/crd/bases/tinkerbell.org_workflows.yaml index abe5e82af..60c6f766d 100644 --- a/config/crd/bases/tinkerbell.org_workflows.yaml +++ b/config/crd/bases/tinkerbell.org_workflows.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.11.4 + controller-gen.kubebuilder.io/version: v0.14.0 name: workflows.tinkerbell.org spec: group: tinkerbell.org @@ -30,10 +30,19 @@ spec: description: Workflow is the Schema for the Workflows API. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -144,42 +153,66 @@ spec: name: v1alpha2 schema: openAPIV3Schema: - description: Workflow describes a set of actions to be run on a specific Hardware. Workflows execute once and should be considered ephemeral. + description: |- + Workflow describes a set of actions to be run on a specific Hardware. Workflows execute + once and should be considered ephemeral. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object spec: properties: hardwareRef: - description: HardwareRef is a reference to a Hardware resource this workflow will execute on. If no namespace is specified the Workflow's namespace is assumed. + description: HardwareRef is a reference to a Hardware resource this workflow will execute on. properties: name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object x-kubernetes-map-type: atomic templateParams: additionalProperties: type: string - description: "TemplateParams are a list of key-value pairs that are injected into templates at render time. TemplateParams are exposed to templates using a top level .Params key. \n For example, TemplateParams = {\"foo\": \"bar\"}, the foo key can be accessed via .Params.foo." + description: |- + TemplateParams are a list of key-value pairs that are injected into templates at render + time. TemplateParams are exposed to templates using a top level .Params key. + + + For example, TemplateParams = {"foo": "bar"}, the foo key can be accessed via .Params.foo. type: object templateRef: - description: TemplateRef is a reference to a Template resource used to render workflow actions. If no namespace is specified the Workflow's namespace is assumed. + description: TemplateRef is a reference to a Template resource used to render workflow actions. properties: name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? type: string type: object x-kubernetes-map-type: atomic timeout: default: 0 - description: TimeoutSeconds defines the time the workflow has to complete. The timer begins when the first action is requested. When set to 0, no timeout is applied. + description: |- + TimeoutSeconds defines the time the workflow has to complete. The timer begins when the first + action is requested. When set to 0, no timeout is applied. format: int64 minimum: 0 type: integer @@ -192,10 +225,14 @@ spec: description: ActionStatus describes status information about an action. properties: failureMessage: - description: FailureMessage is a free-form user friendly message describing why the Action entered the ActionStateFailed state. Typically, this is an elaboration on the Reason. + description: |- + FailureMessage is a free-form user friendly message describing why the Action entered the + ActionStateFailed state. Typically, this is an elaboration on the Reason. type: string failureReason: - description: FailureReason is a short CamelCase word or phrase describing why the Action entered ActionStateFailed. + description: |- + FailureReason is a short CamelCase word or phrase describing why the Action entered + ActionStateFailed. type: string id: description: ID uniquely identifies the action status. @@ -208,12 +245,16 @@ spec: description: Rendered is the rendered action. properties: args: - description: Args are a set of arguments to be passed to the command executed by the container on launch. + description: |- + Args are a set of arguments to be passed to the command executed by the container on + launch. items: type: string type: array cmd: - description: Cmd defines the command to use when launching the image. It overrides the default command of the action. It must be a unix path to an executable program. + description: |- + Cmd defines the command to use when launching the image. It overrides the default command + of the action. It must be a unix path to an executable program. pattern: ^(/[^/ ]*)+/?$ type: string env: @@ -240,7 +281,7 @@ spec: volumes: description: Volumes defines the volumes to mount into the container. items: - description: "Volume is a specification for mounting a volume in an action. Volumes take the form {SRC-VOLUME-NAME | SRC-HOST-DIR}:TGT-CONTAINER-DIR:OPTIONS. When specifying a VOLUME-NAME that does not exist it will be created for you. Examples: \n Read-only bind mount bound to /data \n /etc/data:/data:ro \n Writable volume name bound to /data \n shared_volume:/data \n See https://docs.docker.com/storage/volumes/ for additional details." + description: "Volume is a specification for mounting a volume in an action. Volumes take the form\n{SRC-VOLUME-NAME | SRC-HOST-DIR}:TGT-CONTAINER-DIR:OPTIONS. When specifying a VOLUME-NAME that\ndoes not exist it will be created for you. Examples:\n\n\nRead-only bind mount bound to /data\n\n\n\t/etc/data:/data:ro\n\n\nWritable volume name bound to /data\n\n\n\tshared_volume:/data\n\n\nSee https://docs.docker.com/storage/volumes/ for additional details." type: string type: array required: @@ -248,7 +289,9 @@ spec: - name type: object startedAt: - description: StartedAt is the time the action was started as reported by the client. Nil indicates the Action has not started. + description: |- + StartedAt is the time the action was started as reported by the client. Nil indicates the + Action has not started. format: date-time type: string state: @@ -261,7 +304,9 @@ spec: conditions: description: Conditions details a set of observations about the Workflow. items: - description: Condition defines an observation on a resource that is generally attainable by inspecting other status fields. + description: |- + Condition defines an observation on a resource that is generally attainable by inspecting + other status fields. properties: lastTransitionTime: description: LastTransition is the last time the condition transitioned from one status to another. @@ -290,15 +335,16 @@ spec: format: date-time type: string startedAt: - description: StartedAt is the time the first action was requested. Nil indicates the Workflow has not started. + description: |- + StartedAt is the time the first action was requested. Nil indicates the Workflow has not + started. format: date-time type: string state: - description: State describes the current state of the workflow. For the workflow to enter the WorkflowStateSucceeded state all actions must be in ActionStateSucceeded. The Workflow will enter a WorkflowStateFailed if 1 or more Actions fails. + description: State describes the current state of the Workflow. type: string required: - actions - - conditions type: object type: object served: false diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 5cdbeda75..c25446a6e 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -1,16 +1,21 @@ # Adds namespace to all resources. namespace: tink-system + # Value of this field is prepended to the # names of all resources, e.g. a deployment named # "wordpress" becomes "alices-wordpress". # Note that it should also match with the prefix (text before '-') of the namespace # field above. namePrefix: tink- + resources: - - namespace.yaml -bases: - - ../crd - - ../rbac - - ../manager - - ../server - - ../server-rbac +- namespace.yaml +- ../crd +- ../manager-rbac +- ../manager +- ../server-rbac +- ../server + + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml new file mode 100644 index 000000000..70c3437f4 --- /dev/null +++ b/config/default/manager_auth_proxy_patch.yaml @@ -0,0 +1,39 @@ +# This patch inject a sidecar container which is a HTTP proxy for the +# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: kube-rbac-proxy + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.15.0 + args: + - "--secure-listen-address=0.0.0.0:8443" + - "--upstream=http://127.0.0.1:8080/" + - "--logtostderr=true" + - "--v=0" + ports: + - containerPort: 8443 + protocol: TCP + name: https + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + - name: manager + args: + - "--health-probe-bind-address=:8081" + - "--metrics-bind-address=127.0.0.1:8080" + - "--leader-elect" diff --git a/config/default/manager_config_patch.yaml b/config/default/manager_config_patch.yaml new file mode 100644 index 000000000..f6f589169 --- /dev/null +++ b/config/default/manager_config_patch.yaml @@ -0,0 +1,10 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager diff --git a/config/manager-rbac/auth_proxy_client_clusterrole.yaml b/config/manager-rbac/auth_proxy_client_clusterrole.yaml new file mode 100644 index 000000000..51a75db47 --- /dev/null +++ b/config/manager-rbac/auth_proxy_client_clusterrole.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metrics-reader +rules: +- nonResourceURLs: + - "/metrics" + verbs: + - get diff --git a/config/manager-rbac/auth_proxy_role.yaml b/config/manager-rbac/auth_proxy_role.yaml new file mode 100644 index 000000000..80e1857c5 --- /dev/null +++ b/config/manager-rbac/auth_proxy_role.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: proxy-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/config/manager-rbac/auth_proxy_role_binding.yaml b/config/manager-rbac/auth_proxy_role_binding.yaml new file mode 100644 index 000000000..ec7acc0a1 --- /dev/null +++ b/config/manager-rbac/auth_proxy_role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: proxy-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/manager-rbac/auth_proxy_service.yaml b/config/manager-rbac/auth_proxy_service.yaml new file mode 100644 index 000000000..9c4cb5d01 --- /dev/null +++ b/config/manager-rbac/auth_proxy_service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + name: controller-manager-metrics-service + namespace: system +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + selector: + control-plane: controller-manager diff --git a/config/rbac/kustomization.yaml b/config/manager-rbac/kustomization.yaml similarity index 100% rename from config/rbac/kustomization.yaml rename to config/manager-rbac/kustomization.yaml diff --git a/config/manager-rbac/leader_election_role.yaml b/config/manager-rbac/leader_election_role.yaml new file mode 100644 index 000000000..4190ec805 --- /dev/null +++ b/config/manager-rbac/leader_election_role.yaml @@ -0,0 +1,37 @@ +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: leader-election-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch diff --git a/config/rbac/leader_election_role_binding.yaml b/config/manager-rbac/leader_election_role_binding.yaml similarity index 100% rename from config/rbac/leader_election_role_binding.yaml rename to config/manager-rbac/leader_election_role_binding.yaml diff --git a/config/manager-rbac/role.yaml b/config/manager-rbac/role.yaml new file mode 100644 index 000000000..fb55a1945 --- /dev/null +++ b/config/manager-rbac/role.yaml @@ -0,0 +1,46 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: manager-role +rules: +- apiGroups: + - tinkerbell.org + resources: + - hardware + - hardware/status + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - tinkerbell.org + resources: + - templates + - templates/status + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - tinkerbell.org + resources: + - workflows + - workflows/finalizers + verbs: + - update +- apiGroups: + - tinkerbell.org + resources: + - workflows + - workflows/status + verbs: + - get + - list + - patch + - update + - watch diff --git a/config/rbac/role_binding.yaml b/config/manager-rbac/role_binding.yaml similarity index 100% rename from config/rbac/role_binding.yaml rename to config/manager-rbac/role_binding.yaml diff --git a/config/rbac/service_account.yaml b/config/manager-rbac/service_account.yaml similarity index 100% rename from config/rbac/service_account.yaml rename to config/manager-rbac/service_account.yaml diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml deleted file mode 100644 index c0a3313c3..000000000 --- a/config/rbac/leader_election_role.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# permissions to do leader election. -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: leader-election-role -rules: - - apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch - - create - - update - - patch - - apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete - - apiGroups: - - "" - resources: - - events - verbs: - - create - - patch diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml deleted file mode 100644 index 295075a5c..000000000 --- a/config/rbac/role.yaml +++ /dev/null @@ -1,40 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - creationTimestamp: null - name: manager-role -rules: - - apiGroups: - - tinkerbell.org - resources: - - hardware - - hardware/status - verbs: - - get - - list - - patch - - update - - watch - - apiGroups: - - tinkerbell.org - resources: - - templates - - templates/status - verbs: - - get - - list - - patch - - update - - watch - - apiGroups: - - tinkerbell.org - resources: - - workflows - - workflows/status - verbs: - - delete - - get - - list - - patch - - update - - watch diff --git a/config/tink-controller-v1alpha2/crd_patch.json b/config/tink-controller-v1alpha2/crd_patch.json new file mode 100644 index 000000000..79db432df --- /dev/null +++ b/config/tink-controller-v1alpha2/crd_patch.json @@ -0,0 +1,22 @@ +[ + { + "op": "replace", + "path": "/spec/versions/0/served", + "value": false + }, + { + "op": "replace", + "path": "/spec/versions/0/storage", + "value": false + }, + { + "op": "replace", + "path": "/spec/versions/1/served", + "value": true + }, + { + "op": "replace", + "path": "/spec/versions/1/storage", + "value": true + } +] \ No newline at end of file diff --git a/config/tink-controller-v1alpha2/kustomization.yaml b/config/tink-controller-v1alpha2/kustomization.yaml new file mode 100644 index 000000000..4bb67b9e9 --- /dev/null +++ b/config/tink-controller-v1alpha2/kustomization.yaml @@ -0,0 +1,25 @@ +resources: +- ../default + +patches: +- target: + group: apiextensions.k8s.io + version: v1 + kind: CustomResourceDefinition + name: workflows.tinkerbell.org + path: crd_patch.json +- target: + group: apiextensions.k8s.io + version: v1 + kind: CustomResourceDefinition + name: templates.tinkerbell.org + path: crd_patch.json +- target: + group: apiextensions.k8s.io + version: v1 + kind: CustomResourceDefinition + name: hardware.tinkerbell.org + path: crd_patch.json + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization diff --git a/go.mod b/go.mod index 086fe2ca7..d1e53c9f0 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/go-logr/zerologr v1.2.3 github.com/google/go-cmp v0.6.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 + github.com/kr/pretty v0.3.1 github.com/onsi/ginkgo/v2 v2.15.0 github.com/onsi/gomega v1.31.1 github.com/opencontainers/image-spec v1.1.0-rc5 @@ -70,6 +71,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kr/text v0.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -86,6 +88,7 @@ require ( github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect diff --git a/go.sum b/go.sum index d50f90324..c2c0b34ab 100644 --- a/go.sum +++ b/go.sum @@ -700,6 +700,7 @@ github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdU github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -748,6 +749,7 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= diff --git a/internal/controller/manager.go b/internal/deprecated/controller/manager.go similarity index 100% rename from internal/controller/manager.go rename to internal/deprecated/controller/manager.go diff --git a/internal/e2e/tink_suite_test.go b/internal/e2e/tink_suite_test.go index c41008e46..9ffec3b64 100644 --- a/internal/e2e/tink_suite_test.go +++ b/internal/e2e/tink_suite_test.go @@ -14,7 +14,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/tinkerbell/tink/api/v1alpha1" - "github.com/tinkerbell/tink/internal/controller" + "github.com/tinkerbell/tink/internal/deprecated/controller" "github.com/tinkerbell/tink/internal/grpcserver" "github.com/tinkerbell/tink/internal/server" "go.uber.org/zap" diff --git a/internal/server/kubernetes_api.go b/internal/server/kubernetes_api.go index a5a8771a5..0fdec5cd2 100644 --- a/internal/server/kubernetes_api.go +++ b/internal/server/kubernetes_api.go @@ -8,7 +8,7 @@ import ( "github.com/go-logr/logr" "github.com/go-logr/zapr" "github.com/tinkerbell/tink/api/v1alpha1" - "github.com/tinkerbell/tink/internal/controller" + "github.com/tinkerbell/tink/internal/deprecated/controller" "github.com/tinkerbell/tink/internal/proto" "go.uber.org/zap" "google.golang.org/grpc" diff --git a/internal/workflow/internal/reconcile.go b/internal/workflow/internal/reconcile.go new file mode 100644 index 000000000..d2a1da2e5 --- /dev/null +++ b/internal/workflow/internal/reconcile.go @@ -0,0 +1,121 @@ +package internal + +import ( + "bytes" + "context" + "text/template" + "time" + + "github.com/go-logr/logr" + "github.com/google/uuid" + tinkv1 "github.com/tinkerbell/tink/api/v1alpha2" + "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// ReconciliationContext reconciles Workflow resources when created or updated. +type ReconciliationContext struct { + // Workflow is the Workflow instance we're reconciling. + Workflow *tinkv1.Workflow + + // NewActionID generated unique IDs for actions. Defaults to generating UUIDv4s. + NewActionID func() string + + Log logr.Logger + Client client.Client +} + +// Reconcile reconciles the Workflow. +func (rc ReconciliationContext) Reconcile(ctx context.Context) (reconcile.Result, error) { + tmplRef := client.ObjectKey{ + Name: rc.Workflow.Spec.TemplateRef.Name, + Namespace: rc.Workflow.Namespace, + } + var tmpl tinkv1.Template + if err := rc.Client.Get(ctx, tmplRef, &tmpl); err != nil { + if errors.IsNotFound(err) { + // The Template may yet to be submitted to the cluster so just requeue. + rc.Log.Info("Template not found; requeue in 5 seconds", "ref", tmplRef) + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil + } + return reconcile.Result{}, err + } + + hwRef := client.ObjectKey{ + Name: rc.Workflow.Spec.HardwareRef.Name, + Namespace: rc.Workflow.Namespace, + } + var hw tinkv1.Hardware + if err := rc.Client.Get(ctx, hwRef, &hw); err != nil { + if errors.IsNotFound(err) { + // The Hardware may yet to be submitted to the cluster so just requeue. + rc.Log.Info("Hardware not found; requeue in 5 seconds", "ref", tmplRef) + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil + } + return reconcile.Result{}, err + } + + // Only render the template and configure action status if its not been done before. + if len(rc.Workflow.Status.Actions) == 0 { + tmpl, err := rc.renderTemplate(tmpl, &hw) + if err != nil { + return reconcile.Result{}, err + } + + rc.Workflow.Status.Actions = rc.toActionStatus(tmpl.Spec.Actions) + } + + return reconcile.Result{}, nil +} + +func (rc ReconciliationContext) renderTemplate(tpl tinkv1.Template, hw *tinkv1.Hardware) (tinkv1.Template, error) { + tplYAML, err := yaml.Marshal(tpl) + if err != nil { + return tinkv1.Template{}, err + } + + renderer, err := template.New(""). + Option("missingkey=error"). + Funcs(workflowTemplateFuncs). + Parse(string(tplYAML)) + if err != nil { + return tinkv1.Template{}, err + } + + tplData := map[string]any{ + "Hardware": hw.Spec, + "Param": rc.Workflow.Spec.TemplateParams, + } + + var renderedTplYAML bytes.Buffer + if err := renderer.Execute(&renderedTplYAML, tplData); err != nil { + return tinkv1.Template{}, err + } + + if err := yaml.Unmarshal(renderedTplYAML.Bytes(), &tpl); err != nil { + return tinkv1.Template{}, err + } + + return tpl, nil +} + +func (rc ReconciliationContext) toActionStatus(actions []tinkv1.Action) []tinkv1.ActionStatus { + var status []tinkv1.ActionStatus + for _, action := range actions { + status = append(status, tinkv1.ActionStatus{ + Rendered: action, + ID: rc.newActionID(), + State: tinkv1.ActionStatePending, + }) + } + return status +} + +func (rc ReconciliationContext) newActionID() string { + if rc.NewActionID != nil { + return rc.NewActionID() + } + return uuid.New().String() +} diff --git a/internal/workflow/internal/reconcile_test.go b/internal/workflow/internal/reconcile_test.go new file mode 100644 index 000000000..418351268 --- /dev/null +++ b/internal/workflow/internal/reconcile_test.go @@ -0,0 +1,136 @@ +package internal_test + +import ( + "context" + "os" + "testing" + + "github.com/go-logr/zerologr" + "github.com/google/go-cmp/cmp" + "github.com/rs/zerolog" + "github.com/tinkerbell/tink/api/v1alpha2" + tinkv1 "github.com/tinkerbell/tink/api/v1alpha2" + "github.com/tinkerbell/tink/internal/ptr" + . "github.com/tinkerbell/tink/internal/workflow/internal" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + machineryruntimeutil "k8s.io/apimachinery/pkg/util/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestReconcileContext(t *testing.T) { + ctx := context.Background() + + hw := newHardware(func(*v1alpha2.Hardware) {}) + tmpl := newTemplate(func(t *v1alpha2.Template) { + t.Spec.Actions = []tinkv1.Action{ + { + Name: "action", + Image: "image", + Cmd: ptr.String("{{ .Param.Foo }}"), + }, + } + }) + wrkflw := newWorkflow(func(w *v1alpha2.Workflow) { + w.Spec.HardwareRef = corev1.LocalObjectReference{Name: hw.Name} + w.Spec.TemplateRef = corev1.LocalObjectReference{Name: tmpl.Name} + w.Spec.TemplateParams = map[string]string{"Foo": "Bar"} + }) + + expectWrkflw := wrkflw.DeepCopy() + expectWrkflw.Status.Actions = []tinkv1.ActionStatus{ + { + Rendered: newAction(func(a *tinkv1.Action) { + a.Name = "action" + a.Image = "image" + a.Cmd = ptr.String("Bar") + }), + State: "Pending", + ID: newActionID(), + }, + } + + zl := zerolog.New(os.Stdout) + logger := zerologr.New(&zl) + + scheme := runtime.NewScheme() + machineryruntimeutil.Must(v1alpha2.AddToScheme(scheme)) + + clnt := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(hw, tmpl). + Build() + + reconcileCtx := ReconciliationContext{ + Client: clnt, + Log: logger, + Workflow: wrkflw, + NewActionID: newActionID, + } + _, err := reconcileCtx.Reconcile(ctx) + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(expectWrkflw, wrkflw) { + t.Fatal(cmp.Diff(expectWrkflw, wrkflw)) + } +} + +func newWorkflow(fn func(*v1alpha2.Workflow)) *v1alpha2.Workflow { + w := &tinkv1.Workflow{ + TypeMeta: v1.TypeMeta{ + Kind: "Workflow", + APIVersion: v1alpha2.GroupVersion.String(), + }, + ObjectMeta: v1.ObjectMeta{ + Name: "workflow", + }, + Spec: tinkv1.WorkflowSpec{}, + } + fn(w) + return w +} + +func newTemplate(fn func(*v1alpha2.Template)) *v1alpha2.Template { + t := &tinkv1.Template{ + TypeMeta: v1.TypeMeta{ + Kind: "Template", + APIVersion: v1alpha2.GroupVersion.String(), + }, + ObjectMeta: v1.ObjectMeta{ + Name: "template", + }, + } + fn(t) + return t +} + +func newHardware(fn func(*v1alpha2.Hardware)) *v1alpha2.Hardware { + hw := &tinkv1.Hardware{ + TypeMeta: v1.TypeMeta{ + Kind: "Hardware", + APIVersion: v1alpha2.GroupVersion.String(), + }, + ObjectMeta: v1.ObjectMeta{ + Name: "hardware", + }, + } + fn(hw) + return hw +} + +func newAction(fn func(*v1alpha2.Action)) v1alpha2.Action { + a := tinkv1.Action{ + Args: []string{}, + Env: map[string]string{}, + Volumes: []tinkv1.Volume{}, + } + fn(&a) + return a +} + +func newActionID() string { + return "8659e46f-00ff-40e4-a19b-c8661ca81167" +} diff --git a/internal/workflow/internal/template.go b/internal/workflow/internal/template.go new file mode 100644 index 000000000..f68a46700 --- /dev/null +++ b/internal/workflow/internal/template.go @@ -0,0 +1,35 @@ +package internal + +import ( + "fmt" + "strings" +) + +// workflowTemplateFuncs defines the custom functions available to workflow templates. +var workflowTemplateFuncs = map[string]interface{}{ + "contains": strings.Contains, + "hasPrefix": strings.HasPrefix, + "hasSuffix": strings.HasSuffix, + "formatPartition": formatPartition, +} + +// formatPartition formats a device path with partition for the device type. If it receives an +// unidentifiable device path it returns the dev. +// +// Examples +// +// formatPartition("/dev/nvme0n1", 0) -> /dev/nvme0n1p1 +// formatPartition("/dev/sda", 1) -> /dev/sda1 +// formatPartition("/dev/vda", 2) -> /dev/vda2 +func formatPartition(dev string, partition int) string { + switch { + case strings.HasPrefix(dev, "/dev/nvme"): + return fmt.Sprintf("%vp%v", dev, partition) + case strings.HasPrefix(dev, "/dev/sd"), + strings.HasPrefix(dev, "/dev/vd"), + strings.HasPrefix(dev, "/dev/xvd"), + strings.HasPrefix(dev, "/dev/hd"): + return fmt.Sprintf("%v%v", dev, partition) + } + return dev +} diff --git a/internal/workflow/reconciler.go b/internal/workflow/reconciler.go new file mode 100644 index 000000000..bf00e1b6f --- /dev/null +++ b/internal/workflow/reconciler.go @@ -0,0 +1,73 @@ +package workflow + +import ( + "context" + "time" + + tinkv1 "github.com/tinkerbell/tink/api/v1alpha2" + "github.com/tinkerbell/tink/internal/workflow/internal" + "k8s.io/apimachinery/pkg/api/errors" + kerrors "k8s.io/apimachinery/pkg/util/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// Reconciler reconciles Workflow instances. +type Reconciler struct { + client client.Client + nowFunc func() time.Time +} + +// NewReconciler creates a Reconciler instance. +func NewReconciler(clnt client.Client) *Reconciler { + return &Reconciler{ + client: clnt, + nowFunc: time.Now, + } +} + +// +kubebuilder:rbac:groups=tinkerbell.org,resources=hardware;hardware/status,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=tinkerbell.org,resources=templates;templates/status,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=tinkerbell.org,resources=workflows;workflows/status,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=tinkerbell.org,resources=workflows;workflows/finalizers,verbs=update + +func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (result reconcile.Result, rerr error) { + logger := ctrl.LoggerFrom(ctx) + logger.Info("Reconciling") + + wrkflw := &tinkv1.Workflow{} + if err := r.client.Get(ctx, req.NamespacedName, wrkflw); err != nil { + if errors.IsNotFound(err) { + logger.Info("Workflow not found; discontinuing reconciliation") + } + return reconcile.Result{}, client.IgnoreNotFound(err) + } + + // TODO(chrisdoherty) + if !wrkflw.DeletionTimestamp.IsZero() { + return reconcile.Result{}, nil + } + + rc := internal.ReconciliationContext{ + Client: r.client, + Log: logger, + Workflow: wrkflw.DeepCopy(), + } + + // Always attempt to patch. + defer func() { + if err := r.client.Status().Patch(ctx, rc.Workflow, client.MergeFrom(wrkflw)); err != nil { + rerr = kerrors.NewAggregate([]error{rerr, err}) + } + }() + + return rc.Reconcile(ctx) +} + +func (r *Reconciler) SetupWithManager(mgr manager.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&tinkv1.Workflow{}). + Complete(r) +} diff --git a/internal/workflow/reconciler_test.go b/internal/workflow/reconciler_test.go new file mode 100644 index 000000000..8795a7aa8 --- /dev/null +++ b/internal/workflow/reconciler_test.go @@ -0,0 +1,14 @@ +package workflow + +import ( + "github.com/tinkerbell/tink/api/v1alpha2" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" +) + +var scheme = runtime.NewScheme() + +func init() { + _ = clientgoscheme.AddToScheme(scheme) + _ = v1alpha2.AddToScheme(scheme) +}