Skip to content
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

App and Runtime Differentiation #391

Closed
wants to merge 11 commits into from
Closed

App and Runtime Differentiation #391

wants to merge 11 commits into from

Conversation

IfSentient
Copy link
Contributor

@IfSentient IfSentient commented Sep 5, 2024

This PR is being split into two separate non-draft PR's. This Draft will stay open to show forward context when reviewing other PR's

First PR: PR for Initial App Manifest Implementation

What This PR Does

This PR implements #385

This PR introduces the app package, which contains a few important concepts:

  • App, an interface which describes a unit which is run for an app-platform application. This has a "default" implementation in simple, but can be implemented by a user as they see fit. The interface describes actions which can be taken based on kinds (validate, mutate, convert, etc.) and main loop logic to be run by the wrapper/runner.
  • Provider, an interface for a wrapper/runner to use to get app data and instantiate an app with config based on its (the runner's) runtime.
  • Manifest, an early implementation of the App Manifest, which will likely be iterated on, but is required for the runners to work properly, as they need a way of describing app capability.

There is also a "default" app.App implementation as simple.App, and two "runners,": simple.StandaloneOperator, which functions just like a simple.Operator, and plugin.App, which wraps the app in the plugin gRPC runtime, translating the plugin gRPC calls to app ones (this runner is still slightly WIP as there is not a mechanism at the moment for it to get a kube config on initialization).

CUE codegen now also generates a file-based AppManifest (as a CR), and an in-code manifest to use if a AppManifest CRD is not available in the environment. Admission and conversion capabilities can be specified in the apiResource section of the kind's definition like so:

	apiResource: {
		scope: "Namespaced"
		validation: {
			operations: ["create","update"]
		}
		mutation: {
			operations: ["create","update"]
		}
		conversion: true
	}

An update to the tutorial project's cmd/operator/main.go using the new app setup would look like:

const EnvCfgKey = "environment_config"

func main() {
	// Configure the default logger to use slog
	logging.DefaultLogger = logging.NewSLogLogger(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelDebug,
	}))

	//Load the config from the environment
	cfg, err := LoadConfigFromEnv()
	if err != nil {
		logging.DefaultLogger.With("error", err).Error("Unable to load config from environment")
		panic(err)
	}

	// Load the kube config
	kubeConfig, err := LoadInClusterConfig()
	if err != nil {
		logging.DefaultLogger.With("error", err).Error("Unable to load kubernetes configuration")
		panic(err)
	}

	// Set up the operator configuration (kubeconfig, metrics, tracing, etc.)
	// This is the same as the config for an operator in the old way of doing things, but contains the additional AppConfig to pass on when initializing the App
	operatorConfig := simple.OperatorAppConfig{
		AppConfig: app.AppConfig{
			Kubeconfig: kubeConfig.RestConfig,
			ExtraConfig: map[string]any{
				EnvCfgKey: cfg, // Add the config loaded from the env to the app config just in case we need it
			},
		},
		MetricsConfig: simple.MetricsConfig{
			Enabled: true,
		},
		TracingConfig: simple.TracingConfig{
			Enabled: true,
			OpenTelemetryConfig: simple.OpenTelemetryConfig{
				Host:        cfg.OTelConfig.Host,
				Port:        cfg.OTelConfig.Port,
				ConnType:    simple.OTelConnType(cfg.OTelConfig.ConnType),
				ServiceName: cfg.OTelConfig.ServiceName,
			},
		},
		WebhookConfig: simple.OperatorWebhookConfig{
			Port: cfg.WebhookServer.Port,
			TLSConfig: k8s.TLSConfig{
				CertPath: cfg.WebhookServer.TLSCertPath,
				KeyPath:  cfg.WebhookServer.TLSKeyPath,
			},
		},
	}

	operator, err := simple.NewStandaloneOperator(simple.NewAppProvider(generated.LocalManifest(), newApp))
	if err != nil {
		logging.DefaultLogger.With("error", err).Error("Unable to create operator")
		panic(err)
	}

	stopCh := make(chan struct{})

	// Signal channel
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		<-sigCh
		stopCh <- struct{}{}
	}()

	err = operator.Run(operatorConfig, stopCh)
	if err != nil {
		logging.DefaultLogger.With("error", err).Error("Operator run error")
		panic(err)
	}
	logging.DefaultLogger.Info("Normal operator exit")
}

// newApp is a function which creates a new app.App from an app.AppConfig
func newApp(cfg app.AppConfig) (app.App, error) {
	watcher, err := watchers.NewIssueWatcher()
	if err != nil {
		return nil, err
	}

	return simple.NewApp(simple.AppConfig{
		Kubeconfig: cfg.Kubeconfig,
		ManagedKinds: []simple.AppManagedKind{{
			Kind:    issue.Kind(),
			Watcher: watcher,
			Validator: validators.NewIssueValidator(),
			Mutator: mutators.NewIssueMutator(),
		}},
	})
}

@IfSentient
Copy link
Contributor Author

This PR was superseded by #402

@IfSentient IfSentient closed this Sep 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant