-
Notifications
You must be signed in to change notification settings - Fork 137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v1alpha2 Workflow Controller #874
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know you love pointers ;) so why move away from one here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't optional because we expect the LastTransition to be updated even if we're populating the first status. |
||
|
||
// Reason is a short CamelCase description for the conditions last transition. | ||
// +optional | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
FROM alpine:3.15 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. any reason not to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know. The goal of the PR is to get something functional so I've not experimented. |
||
|
||
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"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
package main | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is v1alpha2 so different from a cmd/cli perspective that we have to break it out entirely? Could a flag be used in the existing tink-controller? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm open to using a single binary. This temporary extra binary just felt simpler to me. There won't be a transition period where both APIs are supported so eventually |
||
|
||
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need a default location for a config file? does this provide much value, you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably not; this is copy-n-paste from the existing tink-controller. If we maintain the temporary v1alpha2 binary, I'll reduce this down to bare minimum so we don't end up with cruft down the road. |
||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not too excited we have to use
tink-controller-v1alpha2
:(