Skip to content

Commit 9ea9a32

Browse files
authored
Merge pull request #3726 from ActiveState/CP-1073
Update CI to not deploy directly to production on release branches
2 parents 5a8ebc8 + 0e57fc8 commit 9ea9a32

4 files changed

Lines changed: 218 additions & 53 deletions

File tree

.github/workflows/build.yml

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,6 @@ jobs:
180180
EOF
181181
)"
182182
env:
183-
CODE_SIGNING_PASSWD: ${{ secrets.CODE_SIGNING_PASSWD }}
184-
MSI_CERT_BASE64: ${{ secrets.MSI_CERT_BASE64 }}
185183
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
186184
JIRA_USERNAME: ${{ secrets.JIRA_EMAIL }}
187185
JIRA_TOKEN: ${{ secrets.JIRA_TOKEN }}
@@ -242,43 +240,6 @@ jobs:
242240
shell: bash
243241
run: parallelize results Build-MCP
244242

245-
- # === Prepare Windows Cert ===
246-
name: Prepare Windows Cert
247-
shell: bash
248-
if: runner.os == 'Windows'
249-
run: |
250-
echo $MSI_CERT_BASE64 | base64 --decode > Cert.p12
251-
env:
252-
MSI_CERT_BASE64: ${{ secrets.MSI_CERT_BASE64 }}
253-
254-
- # === Sign Binaries (Windows only) ===
255-
name: Sign Binaries (Windows only)
256-
shell: bash
257-
if: runner.os == 'Windows' && contains(fromJSON('["refs/heads/beta", "refs/heads/release", "refs/heads/LTS"]'), github.ref)
258-
run: |
259-
export PATH=/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.14/bin/:/c/Program\ Files\ \(x86\)/Windows\ Kits/10/bin/10.0.26100.0/x64:$PATH
260-
261-
signtool.exe sign -fd SHA1 -d "ActiveState State Tool" -f "Cert.p12" -p ${CODE_SIGNING_PASSWD} ./build/state.exe
262-
signtool.exe sign -fd SHA1 -d "ActiveState State Service" -f "Cert.p12" -p ${CODE_SIGNING_PASSWD} ./build/state-svc.exe
263-
signtool.exe sign -fd SHA1 -d "ActiveState State Installer" -f "Cert.p12" -p ${CODE_SIGNING_PASSWD} ./build/state-installer.exe
264-
signtool.exe sign -fd SHA1 -d "ActiveState State Tool Remote Installer" -f "Cert.p12" -p ${CODE_SIGNING_PASSWD} ./build/state-remote-installer.exe
265-
signtool.exe sign -fd SHA1 -d "ActiveState State MCP" -f "Cert.p12" -p ${CODE_SIGNING_PASSWD} ./build/state-mcp.exe
266-
env:
267-
CODE_SIGNING_PASSWD: ${{ secrets.CODE_SIGNING_PASSWD }}
268-
269-
- # === Sign Install Scripts (Windows only) ===
270-
name: Sign Install Scripts (Windows only)
271-
shell: powershell
272-
if: runner.os == 'Windows' && contains(fromJSON('["refs/heads/beta", "refs/heads/release", "refs/heads/LTS"]'), github.ref)
273-
run: |
274-
$branchInfix = $Env:GITHUB_REF.Replace("refs/heads/", "").Replace("release", "")
275-
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
276-
$cert.Import('Cert.p12',$env:CODE_SIGNING_PASSWD,'DefaultKeySet')
277-
Set-AuthenticodeSignature -FilePath build\installers\$branchInfix\install.ps1 -Certificate $cert
278-
Set-AuthenticodeSignature -FilePath build\installers\$branchInfix\legacy-install.ps1 -Certificate $cert
279-
env:
280-
CODE_SIGNING_PASSWD: ${{ secrets.CODE_SIGNING_PASSWD }}
281-
282243
- # === Generate Update ===
283244
name: Generate Update
284245
shell: bash
@@ -318,6 +279,7 @@ jobs:
318279
fi
319280
fi
320281
282+
echo "Deploying for integration tests"
321283
state run deploy-updates
322284
state run deploy-installers
323285
state run deploy-remote-installer
@@ -531,9 +493,18 @@ jobs:
531493
name: Deploy
532494
shell: bash
533495
run: |
534-
state run deploy-updates
535-
state run deploy-installers
536-
state run deploy-remote-installer
496+
# Deploy to staging for release branches, regular deploy for others
497+
if [[ "$GITHUB_REF" == "refs/heads/beta" || "$GITHUB_REF" == "refs/heads/release" || "$GITHUB_REF" =~ ^refs/heads/LTS ]]; then
498+
echo "Deploying to staging directory for release branch: $GITHUB_REF"
499+
DEPLOY_TO_STAGING=true state run deploy-updates
500+
DEPLOY_TO_STAGING=true state run deploy-installers
501+
DEPLOY_TO_STAGING=true state run deploy-remote-installer
502+
else
503+
echo "Deploying normally for non-release branch: $GITHUB_REF"
504+
state run deploy-updates
505+
state run deploy-installers
506+
state run deploy-remote-installer
507+
fi
537508
538509
- # === Cleanup Session Artifacts ===
539510
name: Cleanup Session Artifacts

.github/workflows/release.yml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,21 +48,14 @@ jobs:
4848
shell: bash
4949
timeout-minutes: 15
5050
run: |
51-
echo $MSI_CERT_BASE64 | base64 --decode > Cert.p12
52-
export PATH=/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/:/c/Program\ Files\ \(x86\)/Windows\ Kits/10/bin/10.0.16299.0/x86/:$PATH
53-
5451
GOOS=windows state run build-remote-installer
55-
signtool.exe sign -d "ActiveState State Tool Remote Installer" -f "Cert.p12" -p ${CODE_SIGNING_PASSWD} ./build/state-remote-installer.exe
5652
state run generate-remote-install-deployment windows amd64
5753
5854
GOOS=linux state run build-remote-installer
5955
state run generate-remote-install-deployment linux amd64
6056
6157
GOOS=darwin state run build-remote-installer
6258
state run generate-remote-install-deployment darwin amd64
63-
env:
64-
CODE_SIGNING_PASSWD: ${{ secrets.CODE_SIGNING_PASSWD }}
65-
MSI_CERT_BASE64: ${{ secrets.MSI_CERT_BASE64 }}
6659
6760
- # === Configure AWS credentials ==
6861
name: Configure AWS credentials

activestate.yaml

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,15 @@ scripts:
208208
209209
echo "If using tokens make sure to run 'go run extras/aws-mfa-auth/main.go' on TheHomeRepot first."
210210
211-
go run scripts/ci/s3-deployer/main.go ${constants.BUILD_TARGET_PREFIX_DIR}/update us-east-1 state-tool update/state
211+
PREFIX="update/state"
212+
if [ "$DEPLOY_TO_STAGING" = "true" ]; then
213+
PREFIX="staging/$PREFIX"
214+
echo "Deploying updates to staging: $PREFIX"
215+
else
216+
echo "Deploying updates to production: $PREFIX"
217+
fi
218+
219+
go run scripts/ci/s3-deployer/main.go ${constants.BUILD_TARGET_PREFIX_DIR}/update us-east-1 state-tool $PREFIX
212220
- name: build-install-scripts
213221
language: bash
214222
standalone: true
@@ -247,16 +255,49 @@ scripts:
247255
- name: deploy-installers
248256
language: bash
249257
standalone: true
250-
description: Deploys update files to S3. This steps is automated by CI and should never be ran manually unless you KNOW WHAT YOU'RE DOING.
258+
description: Deploys installer files to S3. This steps is automated by CI and should never be ran manually unless you KNOW WHAT YOU'RE DOING.
251259
value: |
252-
go run scripts/ci/s3-deployer/main.go build/installers us-east-1 state-tool update/state
260+
PREFIX="update/state"
261+
if [ "$DEPLOY_TO_STAGING" = "true" ]; then
262+
PREFIX="staging/$PREFIX"
263+
echo "Deploying installers to staging: $PREFIX"
264+
else
265+
echo "Deploying installers to production: $PREFIX"
266+
fi
267+
268+
go run scripts/ci/s3-deployer/main.go build/installers us-east-1 state-tool $PREFIX
253269
- name: deploy-remote-installer
254270
language: bash
255271
standalone: true
256272
value: |
257273
set -e
258274
$constants.SET_ENV
259-
go run scripts/ci/s3-deployer/main.go $BUILD_TARGET_DIR/remote-installer us-east-1 state-tool remote-installer
275+
276+
PREFIX="remote-installer"
277+
if [ "$DEPLOY_TO_STAGING" = "true" ]; then
278+
PREFIX="staging/$PREFIX"
279+
echo "Deploying remote installer to staging: $PREFIX"
280+
else
281+
echo "Deploying remote installer to production: $PREFIX"
282+
fi
283+
284+
go run scripts/ci/s3-deployer/main.go $BUILD_TARGET_DIR/remote-installer us-east-1 state-tool $PREFIX
285+
- name: promote-staging-to-production
286+
language: bash
287+
standalone: true
288+
description: Promotes files from staging directory to production. This should be run manually after Windows binaries have been signed.
289+
value: |
290+
set -e
291+
echo "Promoting staging files to production..."
292+
echo "WARNING: This will move all files from staging/ to production. Make sure Windows binaries have been signed!"
293+
read -p "Are you sure you want to continue? (y/N): " -n 1 -r
294+
echo
295+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
296+
echo "Promotion cancelled."
297+
exit 1
298+
fi
299+
300+
go run scripts/ci/s3-promoter/main.go us-east-1 state-tool
260301
- name: build-workflow-assets
261302
language: bash
262303
standalone: true

scripts/ci/s3-promoter/main.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"os"
8+
"runtime"
9+
"strings"
10+
11+
"github.com/aws/aws-sdk-go-v2/aws"
12+
"github.com/aws/aws-sdk-go-v2/config"
13+
"github.com/aws/aws-sdk-go-v2/service/s3"
14+
"github.com/aws/aws-sdk-go-v2/service/s3/types"
15+
16+
"github.com/ActiveState/cli/internal/condition"
17+
)
18+
19+
var awsRegionName, awsBucketName, basePrefix string
20+
var client *s3.Client
21+
22+
func main() {
23+
if !condition.InUnitTest() {
24+
if len(os.Args) != 3 && len(os.Args) != 4 {
25+
log.Fatalf("Usage: %s <region-name> <bucket-name> [base-prefix]", os.Args[0])
26+
}
27+
28+
awsRegionName = os.Args[1]
29+
awsBucketName = os.Args[2]
30+
if len(os.Args) == 4 {
31+
basePrefix = os.Args[3]
32+
if basePrefix != "" && !strings.HasSuffix(basePrefix, "/") {
33+
basePrefix += "/"
34+
}
35+
}
36+
37+
run()
38+
}
39+
}
40+
41+
func run() {
42+
fmt.Printf("Promoting staging files to production in bucket: %s\n", awsBucketName)
43+
if basePrefix != "" {
44+
fmt.Printf("Using base prefix: %s\n", basePrefix)
45+
}
46+
47+
createClient()
48+
49+
// List all objects with <basePrefix>staging/ prefix
50+
allObjects, err := listObjectsWithPrefix(basePrefix + "staging/")
51+
if err != nil {
52+
log.Fatalf("Failed to list staging objects: %v", err)
53+
}
54+
55+
// Filter out the root staging directory itself (but keep subdirectories)
56+
var stagingObjects []types.Object
57+
stagingPrefix := basePrefix + "staging/"
58+
for _, obj := range allObjects {
59+
if *obj.Key == stagingPrefix {
60+
continue
61+
}
62+
stagingObjects = append(stagingObjects, obj)
63+
}
64+
65+
if len(stagingObjects) == 0 {
66+
fmt.Println("No staging files found to promote.")
67+
return
68+
}
69+
70+
fmt.Printf("Found %d staging files to promote:\n", len(stagingObjects))
71+
for _, obj := range stagingObjects {
72+
fmt.Printf(" - %s\n", *obj.Key)
73+
}
74+
75+
// Copy each staging object to production location and delete the staging version
76+
for _, obj := range stagingObjects {
77+
stagingKey := *obj.Key
78+
relativeKey := strings.TrimPrefix(stagingKey, basePrefix+"staging/")
79+
destinationKey := basePrefix + "release/" + relativeKey
80+
81+
fmt.Printf("Promoting %s -> %s\n", stagingKey, destinationKey)
82+
83+
err := copyObject(stagingKey, destinationKey)
84+
if err != nil {
85+
log.Fatalf("Failed to copy %s to %s: %v", stagingKey, destinationKey, err)
86+
}
87+
88+
err = deleteObject(stagingKey)
89+
if err != nil {
90+
log.Fatalf("Failed to delete staging object %s: %v", stagingKey, err)
91+
}
92+
}
93+
94+
fmt.Printf("Successfully promoted %d files from staging to production.\n", len(stagingObjects))
95+
}
96+
97+
func createClient() {
98+
var err error
99+
100+
cfg, err := config.LoadDefaultConfig(context.Background(),
101+
config.WithRegion(awsRegionName),
102+
)
103+
if err != nil {
104+
log.Fatalf("failed to load config, %s", err.Error())
105+
}
106+
107+
// For Windows workstations, you might need to handle profile selection differently
108+
if runtime.GOOS == "windows" && !condition.OnCI() {
109+
cfg, err = config.LoadDefaultConfig(context.Background(),
110+
config.WithRegion(awsRegionName),
111+
config.WithSharedConfigProfile("mfa"),
112+
)
113+
if err != nil {
114+
log.Fatalf("failed to load config with profile, %s", err.Error())
115+
}
116+
}
117+
118+
client = s3.NewFromConfig(cfg)
119+
}
120+
121+
func listObjectsWithPrefix(prefix string) ([]types.Object, error) {
122+
var objects []types.Object
123+
124+
paginator := s3.NewListObjectsV2Paginator(client, &s3.ListObjectsV2Input{
125+
Bucket: aws.String(awsBucketName),
126+
Prefix: aws.String(prefix),
127+
})
128+
129+
for paginator.HasMorePages() {
130+
page, err := paginator.NextPage(context.Background())
131+
if err != nil {
132+
return nil, err
133+
}
134+
objects = append(objects, page.Contents...)
135+
}
136+
137+
return objects, nil
138+
}
139+
140+
func copyObject(sourceKey, destinationKey string) error {
141+
copySource := fmt.Sprintf("%s/%s", awsBucketName, sourceKey)
142+
143+
_, err := client.CopyObject(context.Background(), &s3.CopyObjectInput{
144+
Bucket: aws.String(awsBucketName),
145+
CopySource: aws.String(copySource),
146+
Key: aws.String(destinationKey),
147+
ACL: types.ObjectCannedACLPublicRead,
148+
})
149+
150+
return err
151+
}
152+
153+
func deleteObject(key string) error {
154+
_, err := client.DeleteObject(context.Background(), &s3.DeleteObjectInput{
155+
Bucket: aws.String(awsBucketName),
156+
Key: aws.String(key),
157+
})
158+
159+
return err
160+
}

0 commit comments

Comments
 (0)