github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/tiltfile/k8scontext/k8scontext.go (about) 1 package k8scontext 2 3 import ( 4 "fmt" 5 6 "go.starlark.net/starlark" 7 8 "github.com/tilt-dev/clusterid" 9 "github.com/tilt-dev/tilt/internal/k8s" 10 "github.com/tilt-dev/tilt/internal/tiltfile/starkit" 11 "github.com/tilt-dev/tilt/internal/tiltfile/value" 12 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 13 "github.com/tilt-dev/tilt/pkg/model" 14 ) 15 16 // Implements functions for dealing with the Kubernetes context. 17 // Exposes an API for other plugins to get and validate the allowed k8s context. 18 type Plugin struct { 19 context k8s.KubeContext 20 namespace k8s.Namespace 21 env clusterid.Product 22 } 23 24 func NewPlugin(context k8s.KubeContext, namespace k8s.Namespace, env clusterid.Product) Plugin { 25 return Plugin{ 26 context: context, 27 namespace: namespace, 28 env: env, 29 } 30 } 31 32 func (e Plugin) NewState() interface{} { 33 return State{context: e.context, env: e.env} 34 } 35 36 func (e Plugin) OnStart(env *starkit.Environment) error { 37 err := env.AddBuiltin("allow_k8s_contexts", e.allowK8sContexts) 38 if err != nil { 39 return err 40 } 41 42 err = env.AddBuiltin("k8s_context", e.k8sContext) 43 if err != nil { 44 return err 45 } 46 47 err = env.AddBuiltin("k8s_namespace", e.k8sNamespace) 48 if err != nil { 49 return err 50 } 51 return nil 52 } 53 54 func (e Plugin) k8sContext(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 55 return starlark.String(e.context), nil 56 } 57 58 func (e Plugin) k8sNamespace(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 59 return starlark.String(e.namespace), nil 60 } 61 62 func (e Plugin) allowK8sContexts(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 63 var contexts starlark.Value 64 if err := starkit.UnpackArgs(thread, fn.Name(), args, kwargs, 65 "contexts", &contexts, 66 ); err != nil { 67 return nil, err 68 } 69 70 newContexts := []k8s.KubeContext{} 71 for _, c := range value.ValueOrSequenceToSlice(contexts) { 72 switch val := c.(type) { 73 case starlark.String: 74 newContexts = append(newContexts, k8s.KubeContext(val)) 75 default: 76 return nil, fmt.Errorf("allow_k8s_contexts contexts must be a string or a sequence of strings; found a %T", val) 77 78 } 79 } 80 81 err := starkit.SetState(thread, func(existing State) State { 82 return State{ 83 context: existing.context, 84 env: existing.env, 85 allowed: append(newContexts, existing.allowed...), 86 } 87 }) 88 89 return starlark.None, err 90 } 91 92 var _ starkit.StatefulPlugin = &Plugin{} 93 94 type State struct { 95 context k8s.KubeContext 96 env clusterid.Product 97 allowed []k8s.KubeContext 98 } 99 100 func (s State) KubeContext() k8s.KubeContext { 101 return s.context 102 } 103 104 // Returns whether we're allowed to deploy to this kubecontext. 105 // 106 // Checks against a manually specified list and a baked-in list 107 // with known dev cluster names. 108 // 109 // Currently, only the tiltfile executor knows about "allowed" kubecontexts. 110 // 111 // We don't keep this information around after tiltfile execution finishes. 112 // 113 // This is incompatible with the overall technical direction of tilt as an 114 // apiserver. Objects registered via the API (like KubernetesApplys) don't get 115 // this protection. And it's currently only limited to the main Tiltfile. 116 // 117 // A more compatible solution would be to have api server objects 118 // for the kubecontexts that tilt is aware of, and ways to mark them safe. 119 func (s State) IsAllowed(tf *v1alpha1.Tiltfile) bool { 120 if tf.Name != model.MainTiltfileManifestName.String() { 121 return true 122 } 123 124 if s.env == k8s.ProductNone || s.env.IsDevCluster() { 125 return true 126 } 127 128 for _, c := range s.allowed { 129 if c == s.context { 130 return true 131 } 132 } 133 134 return false 135 } 136 137 func MustState(model starkit.Model) State { 138 state, err := GetState(model) 139 if err != nil { 140 panic(err) 141 } 142 return state 143 } 144 145 func GetState(model starkit.Model) (State, error) { 146 var state State 147 err := model.Load(&state) 148 return state, err 149 }