Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b3c5bb3

Browse files
authored
feat: Compute project build parameters (#82)
* feat: Add parameter and jobs database schema This modifies a prior migration which is typically forbidden, but because we're pre-production deployment I felt grouping would be helpful to future contributors. This adds database functions that are required for the provisioner daemon and job queue logic. * feat: Compute project build parameters Adds a projectparameter package to compute build-time project values for a provided scope. This package will be used to return which variables are being used for a build, and can visually indicate the hierarchy to a user. * Fix terraform provisioner * Improve naming, abstract inject to consume scope * Run CI on all branches
1 parent b503c8b commit b3c5bb3

File tree

9 files changed

+969
-191
lines changed

9 files changed

+969
-191
lines changed

.github/workflows/coder.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ on:
1010

1111
pull_request:
1212
branches:
13-
- main
14-
- "release/*"
13+
- "*"
1514

1615
workflow_dispatch:
1716

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package projectparameter
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
"fmt"
8+
9+
"github.com/google/uuid"
10+
"golang.org/x/xerrors"
11+
12+
"github.com/coder/coder/database"
13+
"github.com/coder/coder/provisionersdk/proto"
14+
)
15+
16+
// Scope targets identifiers to pull parameters from.
17+
type Scope struct {
18+
OrganizationID string
19+
ProjectID uuid.UUID
20+
ProjectHistoryID uuid.UUID
21+
UserID string
22+
WorkspaceID uuid.UUID
23+
WorkspaceHistoryID uuid.UUID
24+
}
25+
26+
// Value represents a computed parameter.
27+
type Value struct {
28+
Proto *proto.ParameterValue
29+
// DefaultValue is whether a default value for the scope
30+
// was consumed. This can only be true for projects.
31+
DefaultValue bool
32+
Scope database.ParameterScope
33+
ScopeID string
34+
}
35+
36+
// Compute accepts a scope in which parameter values are sourced.
37+
// These sources are iterated in a hierarchical fashion to determine
38+
// the runtime parameter values for a project.
39+
func Compute(ctx context.Context, db database.Store, scope Scope) ([]Value, error) {
40+
compute := &compute{
41+
db: db,
42+
computedParameterByName: map[string]Value{},
43+
projectHistoryParametersByName: map[string]database.ProjectParameter{},
44+
}
45+
46+
// All parameters for the project version!
47+
projectHistoryParameters, err := db.GetProjectParametersByHistoryID(ctx, scope.ProjectHistoryID)
48+
if errors.Is(err, sql.ErrNoRows) {
49+
// This occurs when the project history has defined
50+
// no parameters, so we have nothing to compute!
51+
return []Value{}, nil
52+
}
53+
if err != nil {
54+
return nil, xerrors.Errorf("get project parameters: %w", err)
55+
}
56+
for _, projectHistoryParameter := range projectHistoryParameters {
57+
compute.projectHistoryParametersByName[projectHistoryParameter.Name] = projectHistoryParameter
58+
}
59+
60+
// Organization parameters come first!
61+
err = compute.inject(ctx, database.GetParameterValuesByScopeParams{
62+
Scope: database.ParameterScopeOrganization,
63+
ScopeID: scope.OrganizationID,
64+
})
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
// Default project parameter values come second!
70+
for _, projectHistoryParameter := range projectHistoryParameters {
71+
if !projectHistoryParameter.DefaultSourceValue.Valid {
72+
continue
73+
}
74+
if !projectHistoryParameter.DefaultDestinationValue.Valid {
75+
continue
76+
}
77+
78+
destinationScheme, err := convertDestinationScheme(projectHistoryParameter.DefaultDestinationScheme)
79+
if err != nil {
80+
return nil, xerrors.Errorf("convert default destination scheme for project history parameter %q: %w", projectHistoryParameter.Name, err)
81+
}
82+
83+
switch projectHistoryParameter.DefaultSourceScheme {
84+
case database.ParameterSourceSchemeData:
85+
compute.computedParameterByName[projectHistoryParameter.Name] = Value{
86+
Proto: &proto.ParameterValue{
87+
DestinationScheme: destinationScheme,
88+
Name: projectHistoryParameter.DefaultDestinationValue.String,
89+
Value: projectHistoryParameter.DefaultSourceValue.String,
90+
},
91+
DefaultValue: true,
92+
Scope: database.ParameterScopeProject,
93+
ScopeID: scope.ProjectID.String(),
94+
}
95+
default:
96+
return nil, xerrors.Errorf("unsupported source scheme for project history parameter %q: %q", projectHistoryParameter.Name, string(projectHistoryParameter.DefaultSourceScheme))
97+
}
98+
}
99+
100+
// Project parameters come third!
101+
err = compute.inject(ctx, database.GetParameterValuesByScopeParams{
102+
Scope: database.ParameterScopeProject,
103+
ScopeID: scope.ProjectID.String(),
104+
})
105+
if err != nil {
106+
return nil, err
107+
}
108+
109+
// User parameters come fourth!
110+
err = compute.inject(ctx, database.GetParameterValuesByScopeParams{
111+
Scope: database.ParameterScopeUser,
112+
ScopeID: scope.UserID,
113+
})
114+
if err != nil {
115+
return nil, err
116+
}
117+
118+
// Workspace parameters come last!
119+
err = compute.inject(ctx, database.GetParameterValuesByScopeParams{
120+
Scope: database.ParameterScopeWorkspace,
121+
ScopeID: scope.WorkspaceID.String(),
122+
})
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
for _, projectHistoryParameter := range compute.projectHistoryParametersByName {
128+
if _, ok := compute.computedParameterByName[projectHistoryParameter.Name]; ok {
129+
continue
130+
}
131+
return nil, NoValueError{
132+
ParameterID: projectHistoryParameter.ID,
133+
ParameterName: projectHistoryParameter.Name,
134+
}
135+
}
136+
137+
values := make([]Value, 0, len(compute.computedParameterByName))
138+
for _, value := range compute.computedParameterByName {
139+
values = append(values, value)
140+
}
141+
return values, nil
142+
}
143+
144+
type compute struct {
145+
db database.Store
146+
computedParameterByName map[string]Value
147+
projectHistoryParametersByName map[string]database.ProjectParameter
148+
}
149+
150+
// Validates and computes the value for parameters; setting the value on "parameterByName".
151+
func (c *compute) inject(ctx context.Context, scopeParams database.GetParameterValuesByScopeParams) error {
152+
scopedParameters, err := c.db.GetParameterValuesByScope(ctx, scopeParams)
153+
if errors.Is(err, sql.ErrNoRows) {
154+
err = nil
155+
}
156+
if err != nil {
157+
return xerrors.Errorf("get %s parameters: %w", scopeParams.Scope, err)
158+
}
159+
160+
for _, scopedParameter := range scopedParameters {
161+
projectHistoryParameter, hasProjectHistoryParameter := c.projectHistoryParametersByName[scopedParameter.Name]
162+
if !hasProjectHistoryParameter {
163+
// Don't inject parameters that aren't defined by the project.
164+
continue
165+
}
166+
167+
_, hasExistingParameter := c.computedParameterByName[scopedParameter.Name]
168+
if hasExistingParameter {
169+
// If a parameter already exists, check if this variable can override it.
170+
// Injection hierarchy is the responsibility of the caller. This check ensures
171+
// project parameters cannot be overridden if already set.
172+
if !projectHistoryParameter.AllowOverrideSource && scopedParameter.Scope != database.ParameterScopeProject {
173+
continue
174+
}
175+
}
176+
177+
destinationScheme, err := convertDestinationScheme(scopedParameter.DestinationScheme)
178+
if err != nil {
179+
return xerrors.Errorf("convert destination scheme: %w", err)
180+
}
181+
182+
switch scopedParameter.SourceScheme {
183+
case database.ParameterSourceSchemeData:
184+
c.computedParameterByName[projectHistoryParameter.Name] = Value{
185+
Proto: &proto.ParameterValue{
186+
DestinationScheme: destinationScheme,
187+
Name: scopedParameter.SourceValue,
188+
Value: scopedParameter.DestinationValue,
189+
},
190+
}
191+
default:
192+
return xerrors.Errorf("unsupported source scheme: %q", string(projectHistoryParameter.DefaultSourceScheme))
193+
}
194+
}
195+
return nil
196+
}
197+
198+
// Converts the database destination scheme to the protobuf version.
199+
func convertDestinationScheme(scheme database.ParameterDestinationScheme) (proto.ParameterDestination_Scheme, error) {
200+
switch scheme {
201+
case database.ParameterDestinationSchemeEnvironmentVariable:
202+
return proto.ParameterDestination_ENVIRONMENT_VARIABLE, nil
203+
case database.ParameterDestinationSchemeProvisionerVariable:
204+
return proto.ParameterDestination_PROVISIONER_VARIABLE, nil
205+
default:
206+
return 0, xerrors.Errorf("unsupported destination scheme: %q", scheme)
207+
}
208+
}
209+
210+
type NoValueError struct {
211+
ParameterID uuid.UUID
212+
ParameterName string
213+
}
214+
215+
func (e NoValueError) Error() string {
216+
return fmt.Sprintf("no value for parameter %q found", e.ParameterName)
217+
}

0 commit comments

Comments
 (0)