Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

spindle: pass secrets to engine as env vars

Signed-off-by: oppiliappan <me@oppi.li>

authored by

oppiliappan and committed by
Tangled
09e364ec 3a42398a

+61 -32
+42 -19
spindle/engine/engine.go
··· 11 11 "sync" 12 12 "time" 13 13 14 + securejoin "github.com/cyphar/filepath-securejoin" 14 15 "github.com/docker/docker/api/types/container" 15 16 "github.com/docker/docker/api/types/image" 16 17 "github.com/docker/docker/api/types/mount" ··· 19 18 "github.com/docker/docker/api/types/volume" 20 19 "github.com/docker/docker/client" 21 20 "github.com/docker/docker/pkg/stdcopy" 21 + "golang.org/x/sync/errgroup" 22 22 "tangled.sh/tangled.sh/core/log" 23 23 "tangled.sh/tangled.sh/core/notifier" 24 24 "tangled.sh/tangled.sh/core/spindle/config" 25 25 "tangled.sh/tangled.sh/core/spindle/db" 26 26 "tangled.sh/tangled.sh/core/spindle/models" 27 + "tangled.sh/tangled.sh/core/spindle/secrets" 27 28 ) 28 29 29 30 const ( ··· 40 37 db *db.DB 41 38 n *notifier.Notifier 42 39 cfg *config.Config 40 + vault secrets.Manager 43 41 44 42 cleanupMu sync.Mutex 45 43 cleanup map[string][]cleanupFunc 46 44 } 47 45 48 - func New(ctx context.Context, cfg *config.Config, db *db.DB, n *notifier.Notifier) (*Engine, error) { 46 + func New(ctx context.Context, cfg *config.Config, db *db.DB, n *notifier.Notifier, vault secrets.Manager) (*Engine, error) { 49 47 dcli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) 50 48 if err != nil { 51 49 return nil, err ··· 60 56 db: db, 61 57 n: n, 62 58 cfg: cfg, 59 + vault: vault, 63 60 } 64 61 65 62 e.cleanup = make(map[string][]cleanupFunc) ··· 71 66 func (e *Engine) StartWorkflows(ctx context.Context, pipeline *models.Pipeline, pipelineId models.PipelineId) { 72 67 e.l.Info("starting all workflows in parallel", "pipeline", pipelineId) 73 68 74 - wg := sync.WaitGroup{} 69 + // extract secrets 70 + var allSecrets []secrets.UnlockedSecret 71 + if didSlashRepo, err := securejoin.SecureJoin(pipeline.RepoOwner, pipeline.RepoName); err == nil { 72 + if res, err := e.vault.GetSecretsUnlocked(secrets.DidSlashRepo(didSlashRepo)); err == nil { 73 + allSecrets = res 74 + } 75 + } 76 + 77 + workflowTimeoutStr := e.cfg.Pipelines.WorkflowTimeout 78 + workflowTimeout, err := time.ParseDuration(workflowTimeoutStr) 79 + if err != nil { 80 + e.l.Error("failed to parse workflow timeout", "error", err, "timeout", workflowTimeoutStr) 81 + workflowTimeout = 5 * time.Minute 82 + } 83 + e.l.Info("using workflow timeout", "timeout", workflowTimeout) 84 + 85 + eg, ctx := errgroup.WithContext(ctx) 75 86 for _, w := range pipeline.Workflows { 76 - wg.Add(1) 77 - go func() error { 78 - defer wg.Done() 87 + eg.Go(func() error { 79 88 wid := models.WorkflowId{ 80 89 PipelineId: pipelineId, 81 90 Name: w.Name, ··· 121 102 defer reader.Close() 122 103 io.Copy(os.Stdout, reader) 123 104 124 - workflowTimeoutStr := e.cfg.Pipelines.WorkflowTimeout 125 - workflowTimeout, err := time.ParseDuration(workflowTimeoutStr) 126 - if err != nil { 127 - e.l.Error("failed to parse workflow timeout", "error", err, "timeout", workflowTimeoutStr) 128 - workflowTimeout = 5 * time.Minute 129 - } 130 - e.l.Info("using workflow timeout", "timeout", workflowTimeout) 131 105 ctx, cancel := context.WithTimeout(ctx, workflowTimeout) 132 106 defer cancel() 133 107 134 - err = e.StartSteps(ctx, w.Steps, wid, w.Image) 108 + err = e.StartSteps(ctx, wid, w, allSecrets) 135 109 if err != nil { 136 110 if errors.Is(err, ErrTimedOut) { 137 111 dbErr := e.db.StatusTimeout(wid, e.n) ··· 147 135 } 148 136 149 137 return nil 150 - }() 138 + }) 151 139 } 152 140 153 - wg.Wait() 141 + if err = eg.Wait(); err != nil { 142 + e.l.Error("failed to run one or more workflows", "err", err) 143 + } else { 144 + e.l.Error("successfully ran full pipeline") 145 + } 154 146 } 155 147 156 148 // SetupWorkflow sets up a new network for the workflow and volumes for ··· 202 186 // ONLY marks pipeline as failed if container's exit code is non-zero. 203 187 // All other errors are bubbled up. 204 188 // Fixed version of the step execution logic 205 - func (e *Engine) StartSteps(ctx context.Context, steps []models.Step, wid models.WorkflowId, image string) error { 189 + func (e *Engine) StartSteps(ctx context.Context, wid models.WorkflowId, w models.Workflow, secrets []secrets.UnlockedSecret) error { 190 + workflowEnvs := ConstructEnvs(w.Environment) 191 + for _, s := range secrets { 192 + workflowEnvs.AddEnv(s.Key, s.Value) 193 + } 206 194 207 - for stepIdx, step := range steps { 195 + for stepIdx, step := range w.Steps { 208 196 select { 209 197 case <-ctx.Done(): 210 198 return ctx.Err() 211 199 default: 212 200 } 213 201 214 - envs := ConstructEnvs(step.Environment) 202 + envs := append(EnvVars(nil), workflowEnvs...) 203 + for k, v := range step.Environment { 204 + envs.AddEnv(k, v) 205 + } 215 206 envs.AddEnv("HOME", workspaceDir) 216 207 e.l.Debug("envs for step", "step", step.Name, "envs", envs.Slice()) 217 208 218 209 hostConfig := hostConfig(wid) 219 210 resp, err := e.docker.ContainerCreate(ctx, &container.Config{ 220 - Image: image, 211 + Image: w.Image, 221 212 Cmd: []string{"bash", "-c", step.Command}, 222 213 WorkingDir: workspaceDir, 223 214 Tty: false,
+9 -12
spindle/models/pipeline.go
··· 8 8 ) 9 9 10 10 type Pipeline struct { 11 + RepoOwner string 12 + RepoName string 11 13 Workflows []Workflow 12 14 } 13 15 ··· 65 63 swf.Environment = workflowEnvToMap(twf.Environment) 66 64 swf.Image = workflowImage(twf.Dependencies, cfg.Pipelines.Nixery) 67 65 68 - swf.addNixProfileToPath() 69 - swf.setGlobalEnvs() 70 66 setup := &setupSteps{} 71 67 72 68 setup.addStep(nixConfStep()) ··· 79 79 80 80 workflows = append(workflows, *swf) 81 81 } 82 - return &Pipeline{Workflows: workflows} 82 + repoOwner := pl.TriggerMetadata.Repo.Did 83 + repoName := pl.TriggerMetadata.Repo.Repo 84 + return &Pipeline{ 85 + RepoOwner: repoOwner, 86 + RepoName: repoName, 87 + Workflows: workflows, 88 + } 83 89 } 84 90 85 91 func workflowEnvToMap(envs []*tangled.Pipeline_Pair) map[string]string { ··· 120 114 dependencies = path.Join(dependencies, "bash", "git", "coreutils", "nix") 121 115 122 116 return path.Join(nixery, dependencies) 123 - } 124 - 125 - func (wf *Workflow) addNixProfileToPath() { 126 - wf.Environment["PATH"] = "$PATH:/.nix-profile/bin" 127 - } 128 - 129 - func (wf *Workflow) setGlobalEnvs() { 130 - wf.Environment["NIX_CONFIG"] = "experimental-features = nix-command flakes" 131 - wf.Environment["HOME"] = "/tangled/workspace" 132 117 }
+3
spindle/models/setup_steps.go
··· 102 102 continue 103 103 } 104 104 105 + if len(packages) == 0 { 106 + customPackages = append(customPackages, registry) 107 + } 105 108 // collect packages from custom registries 106 109 for _, pkg := range packages { 107 110 customPackages = append(customPackages, fmt.Sprintf("'%s#%s'", registry, pkg))
+7 -1
spindle/server.go
··· 68 68 69 69 n := notifier.New() 70 70 71 - eng, err := engine.New(ctx, cfg, d, &n) 71 + // TODO: add hashicorp vault provider and choose here 72 + vault, err := secrets.NewSQLiteManager(cfg.Server.DBPath, secrets.WithTableName("secrets")) 73 + if err != nil { 74 + return fmt.Errorf("failed to setup secrets provider: %w", err) 75 + } 76 + 77 + eng, err := engine.New(ctx, cfg, d, &n, vault) 72 78 if err != nil { 73 79 return err 74 80 }