@@ -41,15 +41,46 @@ var (
41
41
ErrNoChanges = fmt .Errorf ("no changes to commit" )
42
42
)
43
43
44
+ // Deployment is a prepared deployment to a GitOps repository.
45
+ type Deployment struct {
46
+ // Bundle is the deployment bundle being deployed.
47
+ Bundle deployment.ModuleBundle
48
+
49
+ // Manifests is the generated manifests for the deployment.
50
+ // The key is the name of the manifest and the value is the manifest content.
51
+ Manifests map [string ][]byte
52
+
53
+ // RawBundle is the raw representation of the deployment bundle (in CUE).
54
+ RawBundle []byte
55
+
56
+ // Repo is an in-memory clone of the GitOps repository being deployed to.
57
+ Repo repo.GitRepo
58
+
59
+ // Project is the name of the project being deployed.
60
+ Project string
61
+
62
+ logger * slog.Logger
63
+ }
64
+
65
+ // DeployerConfig is the configuration for a Deployer.
44
66
type DeployerConfig struct {
45
- Git DeployerConfigGit
67
+ // Git is the configuration for the GitOps repository.
68
+ Git DeployerConfigGit
69
+
70
+ // RootDir is the root directory in the GitOps repository to deploy to.
46
71
RootDir string
47
72
}
48
73
74
+ // DeployerConfigGit is the configuration for the GitOps repository.
49
75
type DeployerConfigGit struct {
76
+ // Creds is the credentials to use for the GitOps repository.
50
77
Creds common.Secret
51
- Ref string
52
- Url string
78
+
79
+ // Ref is the Git reference to deploy to.
80
+ Ref string
81
+
82
+ // Url is the URL of the GitOps repository.
83
+ Url string
53
84
}
54
85
55
86
// Deployer performs GitOps deployments for projects.
@@ -63,94 +94,116 @@ type Deployer struct {
63
94
ss secrets.SecretStore
64
95
}
65
96
66
- // DeployProject deploys the manifests for a project to the GitOps repository.
67
- func (d * Deployer ) Deploy (projectName string , bundle deployment.ModuleBundle , dryrun bool ) error {
68
- if bundle .Bundle .Env == "prod" {
69
- return fmt .Errorf ("cannot deploy to production environment" )
97
+ // PrepareOptions are options for preparing a deployment.
98
+ type PrepareOptions struct {
99
+ fs afero.Fs
100
+ }
101
+
102
+ // PrepareOption is an option for preparing a deployment.
103
+ type PrepareOption func (* PrepareOptions )
104
+
105
+ // WithFS sets the filesystem to use for preparing a deployment.
106
+ func WithFS (fs afero.Fs ) PrepareOption {
107
+ return func (o * PrepareOptions ) {
108
+ o .fs = fs
70
109
}
110
+ }
71
111
72
- r , err := d .clone ()
73
- if err != nil {
74
- return err
112
+ // CreateDeployment creates a deployment for the given project and bundle.
113
+ func (d * Deployer ) CreateDeployment (
114
+ project string ,
115
+ bundle deployment.ModuleBundle ,
116
+ opts ... PrepareOption ,
117
+ ) (* Deployment , error ) {
118
+ options := PrepareOptions {
119
+ fs : afero .NewMemMapFs (),
120
+ }
121
+ for _ , o := range opts {
122
+ o (& options )
75
123
}
76
124
77
- prjPath := d .buildProjectPath (bundle , projectName )
125
+ r , err := d .clone (d .cfg .Git .Url , d .cfg .Git .Ref , options .fs )
126
+ if err != nil {
127
+ return nil , err
128
+ }
78
129
130
+ prjPath := buildProjectPath (d .cfg .RootDir , project , bundle )
79
131
d .logger .Info ("Checking if project path exists" , "path" , prjPath )
80
132
if err := d .checkProjectPath (prjPath , & r ); err != nil {
81
- return fmt .Errorf ("failed checking project path: %w" , err )
82
- }
83
-
84
- d .logger .Info ("Clearing project path" , "path" , prjPath )
85
- if err := d .clearProjectPath (prjPath , & r ); err != nil {
86
- return fmt .Errorf ("could not clear project path: %w" , err )
133
+ return nil , fmt .Errorf ("failed checking project path: %w" , err )
87
134
}
88
135
89
136
env , err := d .LoadEnv (prjPath , d .ctx , & r )
90
137
if err != nil {
91
- return fmt .Errorf ("could not load environment: %w" , err )
138
+ return nil , fmt .Errorf ("could not load environment: %w" , err )
92
139
}
93
140
94
141
d .logger .Info ("Generating manifests" )
95
142
result , err := d .gen .GenerateBundle (bundle , env )
96
143
if err != nil {
97
- return fmt .Errorf ("could not generate deployment manifests: %w" , err )
144
+ return nil , fmt .Errorf ("could not generate deployment manifests: %w" , err )
145
+ }
146
+
147
+ d .logger .Info ("Clearing project path" , "path" , prjPath )
148
+ if err := d .clearProjectPath (prjPath , & r ); err != nil {
149
+ return nil , fmt .Errorf ("could not clear project path: %w" , err )
98
150
}
99
151
100
- modPath := filepath .Join (prjPath , "mod .cue" )
101
- d .logger .Info ("Writing module " , "path" , modPath )
102
- if err := r .WriteFile (modPath , []byte (result .Module )); err != nil {
103
- return fmt .Errorf ("could not write module : %w" , err )
152
+ bundlePath := filepath .Join (prjPath , "bundle .cue" )
153
+ d .logger .Info ("Writing bundle " , "path" , bundlePath )
154
+ if err := r .WriteFile (bundlePath , []byte (result .Module )); err != nil {
155
+ return nil , fmt .Errorf ("could not write bundle : %w" , err )
104
156
}
105
157
106
- if err := r .StageFile (modPath ); err != nil {
107
- return fmt .Errorf ("could not add module to working tree: %w" , err )
158
+ if err := r .StageFile (bundlePath ); err != nil {
159
+ return nil , fmt .Errorf ("could not add bundle to working tree: %w" , err )
108
160
}
109
161
110
162
for name , result := range result .Manifests {
111
163
manPath := filepath .Join (prjPath , fmt .Sprintf ("%s.yaml" , name ))
112
164
113
165
d .logger .Info ("Writing manifest" , "path" , manPath )
114
166
if err := r .WriteFile (manPath , []byte (result )); err != nil {
115
- return fmt .Errorf ("could not write manifest: %w" , err )
167
+ return nil , fmt .Errorf ("could not write manifest: %w" , err )
116
168
}
117
169
if err := r .StageFile (manPath ); err != nil {
118
- return fmt .Errorf ("could not add manifest to working tree: %w" , err )
170
+ return nil , fmt .Errorf ("could not add manifest to working tree: %w" , err )
119
171
}
120
172
}
121
173
122
- if ! dryrun {
123
- changes , err := r .HasChanges ()
124
- if err != nil {
125
- return fmt .Errorf ("could not check if worktree has changes: %w" , err )
126
- } else if ! changes {
127
- return ErrNoChanges
128
- }
174
+ return & Deployment {
175
+ Bundle : bundle ,
176
+ Manifests : result .Manifests ,
177
+ RawBundle : result .Module ,
178
+ Repo : r ,
179
+ logger : d .logger ,
180
+ }, nil
181
+ }
129
182
130
- d .logger .Info ("Committing changes" )
131
- _ , err = r .Commit (fmt .Sprintf (GIT_MESSAGE , projectName ))
132
- if err != nil {
133
- return fmt .Errorf ("could not commit changes: %w" , err )
134
- }
183
+ // Commit commits the deployment to the GitOps repository.
184
+ func (d * Deployment ) Commit () error {
185
+ d .logger .Info ("Committing changes" )
186
+ _ , err := d .Repo .Commit (fmt .Sprintf (GIT_MESSAGE , d .Project ))
187
+ if err != nil {
188
+ return fmt .Errorf ("could not commit changes: %w" , err )
189
+ }
135
190
136
- d .logger .Info ("Pushing changes" )
137
- if err := r .Push (); err != nil {
138
- return fmt .Errorf ("could not push changes: %w" , err )
139
- }
140
- } else {
141
- d .logger .Info ("Dry-run: not committing or pushing changes" )
142
- d .logger .Info ("Dumping manifests" )
143
- for _ , r := range result .Manifests {
144
- fmt .Println (string (r ))
145
- }
191
+ d .logger .Info ("Pushing changes" )
192
+ if err := d .Repo .Push (); err != nil {
193
+ return fmt .Errorf ("could not push changes: %w" , err )
146
194
}
147
195
148
196
return nil
149
197
}
150
198
151
- // buildProjectPath builds the path to the project in the GitOps repository.
152
- func (d * Deployer ) buildProjectPath (b deployment.ModuleBundle , projectName string ) string {
153
- return fmt .Sprintf (PATH , d .cfg .RootDir , b .Bundle .Env , projectName )
199
+ // HasChanges checks if the deployment results in changes to the GitOps repository.
200
+ func (d * Deployment ) HasChanges () (bool , error ) {
201
+ changes , err := d .Repo .HasChanges ()
202
+ if err != nil {
203
+ return false , fmt .Errorf ("could not check if worktree has changes: %w" , err )
204
+ }
205
+
206
+ return changes , nil
154
207
}
155
208
156
209
// checkProjectPath checks if the project path exists and creates it if it does not.
@@ -195,12 +248,12 @@ func (d *Deployer) clearProjectPath(path string, r *repo.GitRepo) error {
195
248
return nil
196
249
}
197
250
198
- // clone clones the GitOps repository.
199
- func (d * Deployer ) clone () (repo.GitRepo , error ) {
251
+ // clone clones the given repository and returns the GitRepo .
252
+ func (d * Deployer ) clone (url , ref string , fs afero. Fs ) (repo.GitRepo , error ) {
200
253
opts := []repo.GitRepoOption {
201
254
repo .WithAuthor (GIT_NAME , GIT_EMAIL ),
202
255
repo .WithGitRemoteInteractor (d .remote ),
203
- repo .WithFS (d . fs ),
256
+ repo .WithFS (fs ),
204
257
}
205
258
206
259
creds , err := providers .GetGitProviderCreds (& d .cfg .Git .Creds , & d .ss , d .logger )
@@ -210,9 +263,9 @@ func (d *Deployer) clone() (repo.GitRepo, error) {
210
263
opts = append (opts , repo .WithAuth ("forge" , creds .Token ))
211
264
}
212
265
213
- d .logger .Info ("Cloning repository" , "url" , d . cfg . Git . Url , "ref" , d . cfg . Git . Ref )
266
+ d .logger .Info ("Cloning repository" , "url" , url , "ref" , ref )
214
267
r := repo .NewGitRepo (d .logger , opts ... )
215
- if err := r .Clone ("/repo" , d . cfg . Git . Url , d . cfg . Git . Ref ); err != nil {
268
+ if err := r .Clone ("/repo" , url , ref ); err != nil {
216
269
return repo.GitRepo {}, fmt .Errorf ("could not clone repository: %w" , err )
217
270
}
218
271
@@ -278,3 +331,8 @@ func NewDeployerConfigFromProject(p *project.Project) DeployerConfig {
278
331
RootDir : p .Blueprint .Global .Deployment .Root ,
279
332
}
280
333
}
334
+
335
+ // buildProjectPath builds the path to the project in the GitOps repository.
336
+ func buildProjectPath (root string , project string , b deployment.ModuleBundle ) string {
337
+ return fmt .Sprintf (PATH , root , b .Bundle .Env , project )
338
+ }
0 commit comments