From 05399ce7a3ee788797b4b5cffe87cdb0f5bdb8e9 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Tue, 25 Mar 2025 11:54:50 -0400 Subject: [PATCH 01/27] feat: remove the check for existing apps We use the Job ID in our containerID value, and the Job ID will be unique for every job run, so there should never be a circumstance where an existing app will be found. If it didn't hurt to check I would keep this, but the call to cloud.gov is a waste. --- runner-manager/cfd/cloudgov/cloudgov.go | 26 ------------------------- 1 file changed, 26 deletions(-) diff --git a/runner-manager/cfd/cloudgov/cloudgov.go b/runner-manager/cfd/cloudgov/cloudgov.go index 13f27f4..f5bea4f 100644 --- a/runner-manager/cfd/cloudgov/cloudgov.go +++ b/runner-manager/cfd/cloudgov/cloudgov.go @@ -1,12 +1,5 @@ package cloudgov -import ( - "errors" - "fmt" - - "github.com/cloudfoundry/go-cfclient/v3/resource" -) - // Stuff we'll need to implement, for ref // // mapRoute() @@ -113,25 +106,6 @@ func (c *Client) ServicePush(manifest *AppManifest) (*App, error) { return nil, CloudGovClientError{"ServicePush: AppManifest must have Org and Space names"} } - // check for an old instance of the service, delete if found - app, err := c.AppGet(containerID) - if err != nil { - var cferr resource.CloudFoundryError - if errors.As(err, &cferr) { - err = nil - if cferr.Code != 10010 { - return nil, fmt.Errorf("unexpected cferr checking for existing app: %w", cferr) - } - } else { - return nil, fmt.Errorf("error checking for existing service (%v): %w", containerID, err) - } - } - if app != nil { - if err := c.AppDelete(containerID); err != nil { - return nil, fmt.Errorf("error deleting existing service (%v): %w", containerID, err) - } - } - return c.appPush(manifest) } From 9b794373a7a28eba1ce64749537dc29debcffa14 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Wed, 26 Mar 2025 11:42:10 -0400 Subject: [PATCH 02/27] refactor: ServicePush -> Push until reason to diff, clean up Prepare --- runner-manager/cfd/cloudgov/cloudgov.go | 8 ++--- runner-manager/cfd/cmd/drive/prepare.go | 40 +++++++++++++++---------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/runner-manager/cfd/cloudgov/cloudgov.go b/runner-manager/cfd/cloudgov/cloudgov.go index f5bea4f..72fb5c2 100644 --- a/runner-manager/cfd/cloudgov/cloudgov.go +++ b/runner-manager/cfd/cloudgov/cloudgov.go @@ -95,15 +95,15 @@ func (c *Client) AppsList() ([]*App, error) { // TODO: this abstraction might belong in /cmd, // unless it can be further generalized to all pushes -func (c *Client) ServicePush(manifest *AppManifest) (*App, error) { +func (c *Client) Push(manifest *AppManifest) (*App, error) { containerID := manifest.Name if containerID == "" { - return nil, CloudGovClientError{"ServicePush: AppManifest.Name must be defined"} + return nil, CloudGovClientError{"Push: AppManifest.Name must be defined"} } if manifest.OrgName == "" || manifest.SpaceName == "" { - return nil, CloudGovClientError{"ServicePush: AppManifest must have Org and Space names"} + return nil, CloudGovClientError{"Push: AppManifest must have Org and Space names"} } return c.appPush(manifest) @@ -118,7 +118,7 @@ func (c *Client) ServicesPush(manifests []*AppManifest) ([]*App, error) { apps := make([]*App, len(manifests)) for i, s := range manifests { - app, err := c.ServicePush(s) + app, err := c.Push(s) if err != nil { return nil, err } diff --git a/runner-manager/cfd/cmd/drive/prepare.go b/runner-manager/cfd/cmd/drive/prepare.go index 69d27cb..fc32c8c 100644 --- a/runner-manager/cfd/cmd/drive/prepare.go +++ b/runner-manager/cfd/cmd/drive/prepare.go @@ -28,31 +28,43 @@ job log. Read more in GitLab's documentation: https://docs.gitlab.com/runner/executors/custom.html#prepare`, - Run: run, + RunE: run, } type prepStage commonStage -func run(cmd *cobra.Command, args []string) { - // Move this stuff into a setup, add methods. +func run(cmd *cobra.Command, args []string) error { s, err := newStage() if err != nil { - panic(fmt.Errorf("error getting cgClient: %w", err)) + return fmt.Errorf("error initializing prepare stage: %w", err) } - s.prep.startServices() - - // if services, start services + err = s.prep.exec() + if err != nil { + return fmt.Errorf("error executing prepare stage: %w", err) + } - // if os.Getenv("") + return nil +} - // create temp manifest +func (s *prepStage) exec() (err error) { + // Looping service manifests to run `cf push` + err = s.startServices() + if err != nil { + return err + } - // start container + // Pushing the main job config pulled from get_job_config.go + _, err = s.client.Push(s.config.Manifest) + if err != nil { + return err + } + // TODO: // install deps - // allow access to services + + return err } // TODO: refactor to include a service manifests slice and @@ -63,11 +75,7 @@ func (s *prepStage) startServices() error { } for _, serv := range s.config.Services { - s.client.ServicePush(serv.Manifest) - // add docker user/pass - // - // push - // + s.client.Push(serv.Manifest) // map-route containerID apps.internal --hostname containerID // // export WSR_SERVICE_HOST_$alias=$containerID.apps.internal From b1197df1de1c6f0f7536bc5e10a5b2d269ae7710 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Fri, 28 Mar 2025 16:53:52 -0400 Subject: [PATCH 03/27] chore: wip, commiting to save over weekend --- runner-manager/cfd/cloudgov/cf_client.go | 5 +++ runner-manager/cfd/cloudgov/cloudgov.go | 6 ++++ .../cfd/cloudgov/cloudgov_integration_test.go | 17 ++++++++-- runner-manager/cfd/cloudgov/cloudgov_test.go | 2 +- .../cfd/cmd/drive/get_job_config.go | 33 +++++++++++++++++++ .../cfd/cmd/drive/get_job_config_test.go | 25 ++++++++++++++ 6 files changed, 84 insertions(+), 4 deletions(-) diff --git a/runner-manager/cfd/cloudgov/cf_client.go b/runner-manager/cfd/cloudgov/cf_client.go index f516230..971dc1e 100644 --- a/runner-manager/cfd/cloudgov/cf_client.go +++ b/runner-manager/cfd/cloudgov/cf_client.go @@ -104,3 +104,8 @@ func (cf *CFClientAPI) appsList() ([]*App, error) { } return castApps(apps), nil } + +func (cf *CFClientAPI) sshCode() (string, error) { + ctx := context.Background() + return cf.conn().SSHCode(ctx) +} diff --git a/runner-manager/cfd/cloudgov/cloudgov.go b/runner-manager/cfd/cloudgov/cloudgov.go index 72fb5c2..e9623f2 100644 --- a/runner-manager/cfd/cloudgov/cloudgov.go +++ b/runner-manager/cfd/cloudgov/cloudgov.go @@ -13,6 +13,8 @@ type ClientAPI interface { appPush(m *AppManifest) (*App, error) appDelete(id string) error appsList() (apps []*App, err error) + + sshCode() (string, error) } type CredsGetter interface { @@ -127,3 +129,7 @@ func (c *Client) ServicesPush(manifests []*AppManifest) ([]*App, error) { return apps, nil } + +func (c *Client) SSHCode() (string, error) { + return c.sshCode() +} diff --git a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go index f175e7b..524ce77 100644 --- a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go +++ b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go @@ -1,5 +1,3 @@ -//go:build integration - package cloudgov_test import ( @@ -119,7 +117,7 @@ func Test_ServicePush(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { c := cgClient - got, err := c.ServicePush(tt.manifest) + got, err := c.Push(tt.manifest) if err != nil && (tt.wantErr == nil || err.Error() != tt.wantErr.Error()) { t.Errorf("Client.ServicePush() error = %v, wantErr = %v", err, tt.wantErr) return @@ -130,3 +128,16 @@ func Test_ServicePush(t *testing.T) { }) } } + +func Test_SSHCode(t *testing.T) { + c := cgClient + want := "hi" + got, err := c.SSHCode() + if err != nil { + t.Errorf("got error = %v", err) + return + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } +} diff --git a/runner-manager/cfd/cloudgov/cloudgov_test.go b/runner-manager/cfd/cloudgov/cloudgov_test.go index f321036..c61c265 100644 --- a/runner-manager/cfd/cloudgov/cloudgov_test.go +++ b/runner-manager/cfd/cloudgov/cloudgov_test.go @@ -379,7 +379,7 @@ func TestClient_ServicePush(t *testing.T) { ClientAPI: tt.fields.ClientAPI, Opts: tt.fields.Opts, } - got, err := c.ServicePush(tt.args.manifest) + got, err := c.Push(tt.args.manifest) if err != nil || tt.wantErr != nil { if tt.wantErr == nil { t.Errorf("Client.AppsList() error = %v", err) diff --git a/runner-manager/cfd/cmd/drive/get_job_config.go b/runner-manager/cfd/cmd/drive/get_job_config.go index 1336592..ba4338c 100644 --- a/runner-manager/cfd/cmd/drive/get_job_config.go +++ b/runner-manager/cfd/cmd/drive/get_job_config.go @@ -18,6 +18,9 @@ type JobConfig struct { VcapAppData VcapAppJSON string `env:"VCAP_APPLICATION"` + VcapServicesData + VcapServicesJSON string `env:"VCAP_SERVICES"` + Manifest *cloudgov.AppManifest // We combine the following to make the container ID. @@ -69,6 +72,26 @@ type VcapAppData struct { SpaceName string `json:"space_name"` } +type ( + VcapServicesData map[string][]VcapServiceInstance + VcapServiceInstance struct { + Name string + Credentials VcapServiceCredentials + } +) + +type VcapServiceCredentials struct { + Domain string + HTTPPort int + HTTPURI string + HTTPSURI string + CredString string +} + +// TODO: +// echo "$EGRESS_CREDENTIALS" | jq --raw-output ".cred_string" > /home/vcap/app/ssh_proxy.auth +// chmod 0600 /home/vcap/app/ssh_proxy.auth + func parseCfgJSON[R any](j []byte, r *R) (*R, error) { if len(j) < 1 { return r, nil @@ -112,6 +135,13 @@ func (c *JobConfig) parseVcapAppJSON() (err error) { return err } +func (c *JobConfig) parseVcapServicesJSON() (err error) { + ref := &map[string][]VcapServiceInstance{} + ref, err = parseCfgJSON([]byte(c.VcapServicesJSON), ref) + c.VcapServicesData = *ref + return err +} + // This is a pretty simple implementation, if our needs get more // complex we should use one of several existing packages to do this. // e.g., https://pkg.go.dev/github.com/caarlos0/env/v11 @@ -197,6 +227,9 @@ func getJobConfig() (cfg *JobConfig, err error) { if err = cfg.parseVcapAppJSON(); err != nil { return nil, err } + if err = cfg.parseVcapServicesJSON(); err != nil { + return nil, err + } cfg.ContainerID = fmt.Sprintf( "glrw-p%v-c%v-j%v", diff --git a/runner-manager/cfd/cmd/drive/get_job_config_test.go b/runner-manager/cfd/cmd/drive/get_job_config_test.go index 716083f..9640277 100644 --- a/runner-manager/cfd/cmd/drive/get_job_config_test.go +++ b/runner-manager/cfd/cmd/drive/get_job_config_test.go @@ -128,3 +128,28 @@ func Test_parseVcapAppJSON(t *testing.T) { t.Errorf("mismatch (-got +want):\n%s", diff) } } + +func Test_parseVcapServicesJSON(t *testing.T) { + sample := `{"s3":[{"label":"s3","provider":null,"plan":"basic-sandbox","name":"glr-dependency-cache","tags":["AWS","S3","object-storage","terraform-cloudgov-managed"],"instance_guid":"d1541026-64ca-44fb-8a48-39298885ff68","instance_name":"glr-dependency-cache","binding_guid":"9f316c56-a910-4c68-b30c-b42df87fdfec","binding_name":null,"credentials":{"uri":"s3://goooo:booo@s3-fips.us-gov-west-1.amazonaws.com/cg-d1541026-64ca-44fb-8a48-39298885ff68","insecure_skip_verify":false,"access_key_id":"jjjjj","secret_access_key":"ssssssss","region":"us-gov-west-1","bucket":"cg-d1541026-64ca-44fb-8a48-39298885ff68","endpoint":"s3-fips.us-gov-west-1.amazonaws.com","fips_endpoint":"s3-fips.us-gov-west-1.amazonaws.com","additional_buckets":[]},"syslog_drain_url":null,"volume_mounts":[]}],"user-provided":[{"label":"user-provided","name":"glr-egress-proxy-credentials","tags":[],"instance_guid":"608e3f73-40df-4866-8d3a-fd5fda6bedcd","instance_name":"glr-egress-proxy-credentials","binding_guid":"7530ea7b-a04d-4c59-a05f-e07ab3efa573","binding_name":null,"credentials":{"cred_string":"018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ","domain":"vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal","http_port":8080,"http_uri":"http://018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ@vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal:8080","https_uri":"https://018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ@vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal:61443"},"syslog_drain_url":null,"volume_mounts":[]}]}` + t.Setenv("VCAP_SERVICES", sample) + + wanted := VcapServicesData{ + "s3": []VcapServiceInstance{{ + Name: "glr-dependency-cache", + }}, + "user-provided": []VcapServiceInstance{{ + Name: "glr-egress-proxy-credentials", + Credentials: VcapServiceCredentials{Domain: "vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal", HTTPPort: 8080, HTTPURI: "http://018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ@vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal:8080", HTTPSURI: "https://018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ@vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal:8080", CredString: "018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ"}, + }}, + } + + cfg, err := getJobConfig() + if err != nil { + t.Error(err) + return + } + + if diff := cmp.Diff(cfg.VcapServicesData, wanted); diff != "" { + t.Errorf("mismatch (-got +want):\n%s", diff) + } +} From f91235f731b271ca1019dc8c36af31fd7cbf0d0d Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Thu, 3 Apr 2025 11:28:11 -0400 Subject: [PATCH 04/27] feat: add ssh_proxy script --- runner-manager/cfd/sh/ssh_proxy.sh | 94 ++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100755 runner-manager/cfd/sh/ssh_proxy.sh diff --git a/runner-manager/cfd/sh/ssh_proxy.sh b/runner-manager/cfd/sh/ssh_proxy.sh new file mode 100755 index 0000000..d0ccfa7 --- /dev/null +++ b/runner-manager/cfd/sh/ssh_proxy.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +usage=" +$0: Run SSH through egress-proxy with corkscrew. Must be provided password with either -e, -p, or STDIN. + +Usage: + $0 -h + $0 -g -H -P -F [-p |-e] + +Options: +-h show help and exit +-g GUID of the app to connect with +-H egress proxy host +-P egress proxy port +-F egress proxy basic auth credential file +[-p] ssh password +[-e] take password from env var 'SSHPASS' + command to pass to ssh as-is +" + +function exitWithUsage() { + echo "$usage" + exit "$1" +} + +guid="" +proxyHost="" +proxyPort="" +proxyFile="" +sshPass="" + +set -e +while getopts ":hg:H:P:F:p:e" opt; do + case "${opt}" in + g) + guid=${OPTARG} + ;; + H) + proxyHost=${OPTARG} + ;; + P) + proxyPort=${OPTARG} + ;; + F) + proxyFile=${OPTARG} + ;; + p) + sshPass=${OPTARG} + ;; + e) + sshPass=${SSHPASS:?"-e used but SSHPASS undefined, run with -h for usage"} + ;; + h | *) + exitWithUsage + ;; + esac +done +shift $((OPTIND - 1)) + +# read sshPass from stdin +if [[ -z "$sshPass" ]]; then + sshPass=$(cat) +fi + +# still no sshPass, exiting +if [[ -z "$sshPass" ]]; then + echo "error: ssh password is required but none passed with -p, -e or STDIN" + exitWithUsage 1 +fi + +if [[ -z "$guid" ]]; then + echo "error: -g is required" + exitWithUsage 1 +fi + +if [[ -z "$proxyHost" ]]; then + echo "error: -H is required" + exitWithUsage 1 +fi + +if [[ -z "$proxyPort" ]]; then + echo "error: -P is required" + exitWithUsage 1 +fi + +if [[ -z "$proxyFile" ]]; then + echo "error: -F is required" + exitWithUsage 1 +fi + +SSHPASS=$sshPass sshpass -e ssh -p 2222 -T \ + -o "StrictHostKeyChecking=no" \ + -o "ProxyCommand corkscrew $proxyHost $proxyPort %h %p $proxyFile" \ + cf:"$guid"/0@ssh.fr.cloud.gov "$@" From 50c9a95fcdc89dcc623fc784789dc777b737fae4 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Fri, 4 Apr 2025 17:46:05 -0400 Subject: [PATCH 05/27] fix: missed ServicePush -> Push renames, add integration flag back --- runner-manager/cfd/cloudgov/cloudgov_integration_test.go | 6 ++++-- runner-manager/cfd/cloudgov/cloudgov_test.go | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go index 524ce77..7408d1a 100644 --- a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go +++ b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go @@ -1,3 +1,5 @@ +//go:build integration + package cloudgov_test import ( @@ -88,7 +90,7 @@ func Test_CFAdapter_AppGet(t *testing.T) { } } -func Test_ServicePush(t *testing.T) { +func Test_Push(t *testing.T) { tests := map[string]struct { want *cg.App wantErr error @@ -119,7 +121,7 @@ func Test_ServicePush(t *testing.T) { c := cgClient got, err := c.Push(tt.manifest) if err != nil && (tt.wantErr == nil || err.Error() != tt.wantErr.Error()) { - t.Errorf("Client.ServicePush() error = %v, wantErr = %v", err, tt.wantErr) + t.Errorf("Client.Push() error = %v, wantErr = %v", err, tt.wantErr) return } if diff := cmp.Diff(got, tt.want); diff != "" { diff --git a/runner-manager/cfd/cloudgov/cloudgov_test.go b/runner-manager/cfd/cloudgov/cloudgov_test.go index c61c265..0438f55 100644 --- a/runner-manager/cfd/cloudgov/cloudgov_test.go +++ b/runner-manager/cfd/cloudgov/cloudgov_test.go @@ -335,7 +335,7 @@ func TestClient_AppsList(t *testing.T) { } } -func TestClient_ServicePush(t *testing.T) { +func TestClient_Push(t *testing.T) { optsStub := &Opts{CredsGetter: stubCredsGetter{"a", "b", false}} cgStub := &Client{&stubClientAPI{ StURL: apiRootURLDefault, @@ -359,12 +359,12 @@ func TestClient_ServicePush(t *testing.T) { "Fails without name": { fields: fields{ClientAPI: cgStub, Opts: optsStub}, args: args{manifest: &AppManifest{}}, - wantErr: CloudGovClientError{"ServicePush: AppManifest.Name must be defined"}, + wantErr: CloudGovClientError{"Push: AppManifest.Name must be defined"}, }, "Fails without org": { fields: fields{ClientAPI: cgStub, Opts: optsStub}, args: args{manifest: &AppManifest{Name: "Some App"}}, - wantErr: CloudGovClientError{"ServicePush: AppManifest must have Org and Space names"}, + wantErr: CloudGovClientError{"Push: AppManifest must have Org and Space names"}, }, "Passes with all fields": { fields: fields{ClientAPI: cgStub, Opts: optsStub}, @@ -384,7 +384,7 @@ func TestClient_ServicePush(t *testing.T) { if tt.wantErr == nil { t.Errorf("Client.AppsList() error = %v", err) } else if diff := cmp.Diff(tt.wantErr, err, cmpopts.EquateErrors()); diff != "" { - t.Errorf("Client.ServicePush() error mismatch (-want +got):\n%s", diff) + t.Errorf("Client.Push() error mismatch (-want +got):\n%s", diff) } return } From 5feb40662c65bcf4219594145372df409301496e Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Fri, 4 Apr 2025 17:47:00 -0400 Subject: [PATCH 06/27] feat: load egress config into JobConfig --- .../cfd/cmd/drive/get_job_config.go | 65 ++++++++++++--- .../cfd/cmd/drive/get_job_config_test.go | 81 ++++++++++++++----- 2 files changed, 119 insertions(+), 27 deletions(-) diff --git a/runner-manager/cfd/cmd/drive/get_job_config.go b/runner-manager/cfd/cmd/drive/get_job_config.go index ba4338c..689c0d6 100644 --- a/runner-manager/cfd/cmd/drive/get_job_config.go +++ b/runner-manager/cfd/cmd/drive/get_job_config.go @@ -6,6 +6,7 @@ import ( "os" "reflect" "regexp" + "slices" "strings" "github.com/GSA-TTS/gitlab-runner-cloudgov/runner/cfd/cloudgov" @@ -19,7 +20,10 @@ type JobConfig struct { VcapAppJSON string `env:"VCAP_APPLICATION"` VcapServicesData - VcapServicesJSON string `env:"VCAP_SERVICES"` + VcapServicesJSON string `env:"VCAP_SERVICES"` + EgressServiceName string `env:"PROXY_CREDENTIAL_INSTANCE"` + + EgressProxyConfig Manifest *cloudgov.AppManifest @@ -81,16 +85,20 @@ type ( ) type VcapServiceCredentials struct { - Domain string - HTTPPort int - HTTPURI string - HTTPSURI string - CredString string + Domain string `json:"domain"` + HTTPPort int `json:"http_port"` + HTTPURI string `json:"http_uri"` + HTTPSURI string `json:"https_uri"` + CredString string `json:"cred_string"` } -// TODO: -// echo "$EGRESS_CREDENTIALS" | jq --raw-output ".cred_string" > /home/vcap/app/ssh_proxy.auth -// chmod 0600 /home/vcap/app/ssh_proxy.auth +type EgressProxyConfig struct { + ProxyHostHTTP string + ProxyHostHTTPS string + ProxyHostSSH string + ProxyPortSSH int + ProxyAuthFile string +} func parseCfgJSON[R any](j []byte, r *R) (*R, error) { if len(j) < 1 { @@ -212,6 +220,42 @@ func (cfg *JobConfig) processImage(img Image, m *cloudgov.AppManifest) { } } +func (cfg *JobConfig) processEgressProxyCfg() (err error) { + defer (func() { + if err != nil { + err = fmt.Errorf("error processEgressProxyCfg: %w", err) + } + })() + + userServices := cfg.VcapServicesData["user-provided"] + if len(userServices) < 1 { + return nil + } + + egressIdx := slices.IndexFunc(userServices, func(vsi VcapServiceInstance) bool { + return vsi.Name == cfg.EgressServiceName + }) + if egressIdx < 0 { + return nil + } + + esc := userServices[egressIdx].Credentials + + cfg.EgressProxyConfig = EgressProxyConfig{ + ProxyHostHTTP: esc.HTTPURI, + ProxyHostHTTPS: esc.HTTPSURI, + ProxyHostSSH: esc.Domain, + ProxyPortSSH: esc.HTTPPort, + ProxyAuthFile: os.Getenv("PROXY_AUTH_FILE"), + } + + if cfg.ProxyAuthFile == "" { + cfg.ProxyAuthFile = "/home/vcap/app/ssh_proxy.auth" + } + + return os.WriteFile(cfg.ProxyAuthFile, []byte(esc.CredString), 0600) +} + func getJobConfig() (cfg *JobConfig, err error) { defer func() { if err != nil { @@ -230,6 +274,9 @@ func getJobConfig() (cfg *JobConfig, err error) { if err = cfg.parseVcapServicesJSON(); err != nil { return nil, err } + if err = cfg.processEgressProxyCfg(); err != nil { + return nil, err + } cfg.ContainerID = fmt.Sprintf( "glrw-p%v-c%v-j%v", diff --git a/runner-manager/cfd/cmd/drive/get_job_config_test.go b/runner-manager/cfd/cmd/drive/get_job_config_test.go index 9640277..d2b379b 100644 --- a/runner-manager/cfd/cmd/drive/get_job_config_test.go +++ b/runner-manager/cfd/cmd/drive/get_job_config_test.go @@ -1,6 +1,8 @@ package drive import ( + "os" + "path/filepath" "testing" "github.com/GSA-TTS/gitlab-runner-cloudgov/runner/cfd/cloudgov" @@ -12,16 +14,17 @@ import ( // think about that later. func Test_GetJobConfig(t *testing.T) { cfgWant := &JobConfig{ - JobResponse: JobResponse{}, - CIRegistryUser: "foo", - CIRegistryPass: "bar", - DockerHubUser: "foo", - DockerHubToken: "1234", - WorkerMemory: "1024M", - WorkerDiskSize: "1024M", - JobResponseFile: "", - VcapAppJSON: "", - ContainerID: "glrw-p-c-j", + JobResponse: JobResponse{}, + CIRegistryUser: "foo", + CIRegistryPass: "bar", + DockerHubUser: "foo", + DockerHubToken: "1234", + WorkerMemory: "1024M", + WorkerDiskSize: "1024M", + JobResponseFile: "", + VcapAppJSON: "", + VcapServicesData: VcapServicesData{}, + ContainerID: "glrw-p-c-j", Manifest: &cloudgov.AppManifest{ Name: "glrw-p-c-j", NoRoute: true, @@ -50,7 +53,7 @@ func Test_GetJobConfig(t *testing.T) { t.Error(err) return } - if diff := cmp.Diff(cfgWant, parsedCfg); diff != "" { + if diff := cmp.Diff(parsedCfg, cfgWant); diff != "" { t.Error(diff) } } @@ -80,8 +83,9 @@ func Test_parseJobResponseFile(t *testing.T) { Process: cloudgov.AppManifestProcess{Command: "j k l g h i", HealthCheckType: "process"}, }, Config: &JobConfig{ - ContainerID: "glrw-p-c-j", - JobResponseFile: "./testdata/sample_job_response.json", + ContainerID: "glrw-p-c-j", + JobResponseFile: "./testdata/sample_job_response.json", + VcapServicesData: VcapServicesData{}, Manifest: &cloudgov.AppManifest{ Name: "glrw-p-c-j", Env: map[string]string{"foo": "bar"}, @@ -130,26 +134,67 @@ func Test_parseVcapAppJSON(t *testing.T) { } func Test_parseVcapServicesJSON(t *testing.T) { - sample := `{"s3":[{"label":"s3","provider":null,"plan":"basic-sandbox","name":"glr-dependency-cache","tags":["AWS","S3","object-storage","terraform-cloudgov-managed"],"instance_guid":"d1541026-64ca-44fb-8a48-39298885ff68","instance_name":"glr-dependency-cache","binding_guid":"9f316c56-a910-4c68-b30c-b42df87fdfec","binding_name":null,"credentials":{"uri":"s3://goooo:booo@s3-fips.us-gov-west-1.amazonaws.com/cg-d1541026-64ca-44fb-8a48-39298885ff68","insecure_skip_verify":false,"access_key_id":"jjjjj","secret_access_key":"ssssssss","region":"us-gov-west-1","bucket":"cg-d1541026-64ca-44fb-8a48-39298885ff68","endpoint":"s3-fips.us-gov-west-1.amazonaws.com","fips_endpoint":"s3-fips.us-gov-west-1.amazonaws.com","additional_buckets":[]},"syslog_drain_url":null,"volume_mounts":[]}],"user-provided":[{"label":"user-provided","name":"glr-egress-proxy-credentials","tags":[],"instance_guid":"608e3f73-40df-4866-8d3a-fd5fda6bedcd","instance_name":"glr-egress-proxy-credentials","binding_guid":"7530ea7b-a04d-4c59-a05f-e07ab3efa573","binding_name":null,"credentials":{"cred_string":"018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ","domain":"vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal","http_port":8080,"http_uri":"http://018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ@vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal:8080","https_uri":"https://018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ@vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal:61443"},"syslog_drain_url":null,"volume_mounts":[]}]}` + sample := `{"s3":[{"label":"s3","provider":null,"plan":"basic-sandbox","name":"glr-dependency-cache","tags":["AWS","S3","object-storage","terraform-cloudgov-managed"],"instance_guid":"d1541026","instance_name":"glr-dependency-cache","binding_guid":"9f316c56","binding_name":null,"credentials":{"uri":"s3://goooo:booo@s3-fips.us-gov-west-1.aaws.com/cg-d1541026","insecure_skip_verify":false,"access_key_id":"jjjjj","secret_access_key":"ssssssss","region":"us-gov-west-1","bucket":"cg-d1541026","endpoint":"s3-fips.us-gov-west-1.amazonaws.com","fips_endpoint":"s3-fips.us-gov-west-1.amazonaws.com","additional_buckets":[]},"syslog_drain_url":null,"volume_mounts":[]}],"user-provided":[{"label":"user-provided","name":"glr-egress-proxy-credentials","tags":[],"instance_guid":"608e3f73","instance_name":"glr-egress-proxy-credentials","binding_guid":"7530ea7b","binding_name":null,"credentials":{"cred_string":"018052ba:ukgZ","domain":"egress-proxy.apps.internal","http_port":8080,"http_uri":"http://018052b:ukHK@egress-proxy.apps.internal:8080","https_uri":"https://018052ba:ukHK1@egress-proxy.apps.internal:61443"},"syslog_drain_url":null,"volume_mounts":[]}]}` t.Setenv("VCAP_SERVICES", sample) + t.Setenv("PROXY_CREDENTIAL_INSTANCE", "glr-egress-proxy-credentials") - wanted := VcapServicesData{ + dir, err := os.MkdirTemp("", "temp_auth_files") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + authFile := filepath.Join(dir, "ssh_proxy.auth") + t.Setenv("PROXY_AUTH_FILE", authFile) + + credStringWanted := "018052ba:ukgZ" + + wantedServices := VcapServicesData{ "s3": []VcapServiceInstance{{ Name: "glr-dependency-cache", }}, "user-provided": []VcapServiceInstance{{ - Name: "glr-egress-proxy-credentials", - Credentials: VcapServiceCredentials{Domain: "vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal", HTTPPort: 8080, HTTPURI: "http://018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ@vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal:8080", HTTPSURI: "https://018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ@vtools-prototyping-devtools-staging-glr-egress-glr-egress-proxy.apps.internal:8080", CredString: "018052ba-ab88-cd96-e1fb-146be5abd727:ukHK19mbG5i5JrgZ"}, + Name: "glr-egress-proxy-credentials", + Credentials: VcapServiceCredentials{ + Domain: "egress-proxy.apps.internal", + HTTPPort: 8080, + HTTPURI: "http://018052b:ukHK@egress-proxy.apps.internal:8080", + HTTPSURI: "https://018052ba:ukHK1@egress-proxy.apps.internal:61443", + CredString: credStringWanted, + }, }}, } + wantedEgressConfig := EgressProxyConfig{ + ProxyHostHTTP: "http://018052b:ukHK@egress-proxy.apps.internal:8080", + ProxyHostHTTPS: "https://018052ba:ukHK1@egress-proxy.apps.internal:61443", + ProxyHostSSH: "egress-proxy.apps.internal", + ProxyPortSSH: 8080, + ProxyAuthFile: authFile, + } + cfg, err := getJobConfig() if err != nil { t.Error(err) return } - if diff := cmp.Diff(cfg.VcapServicesData, wanted); diff != "" { + if diff := cmp.Diff(cfg.VcapServicesData, wantedServices); diff != "" { t.Errorf("mismatch (-got +want):\n%s", diff) } + + if diff := cmp.Diff(cfg.VcapServicesData["user-provided"][0], wantedServices["user-provided"][0]); diff != "" { + t.Fatalf("mismatch (-got +want):\n%s", diff) + } + + if diff := cmp.Diff(cfg.EgressProxyConfig, wantedEgressConfig); diff != "" { + t.Fatalf("mismatch (-got +want):\n%s", diff) + } + + credString, err := os.ReadFile(cfg.ProxyAuthFile) + if err != nil { + t.Fatalf("error reading ProxyAuthFile: %v", err) + } + if diff := cmp.Diff(string(credString), credStringWanted); diff != "" { + t.Fatalf("mismatch (-got +want):\n%s", diff) + } } From 5b5e5dd6458e12b050828fd8b49cb3935d5bf188 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Mon, 16 Jun 2025 14:41:43 -0400 Subject: [PATCH 07/27] refactor: move the go module to the root directory This fixes some frustrating tooling issues I was having because the Go root and the Git root were in different places, non-standard for Go. --- .github/workflows/cf-driver-go.yml | 9 +-------- runner-manager/cfd/Makefile => Makefile | 0 runner-manager/cfd/go.mod => go.mod | 2 +- runner-manager/cfd/go.sum => go.sum | 2 ++ runner-manager/cfd/cloudgov/cloudgov_integration_test.go | 2 +- runner-manager/cfd/cmd/drive/get_job_config.go | 2 +- runner-manager/cfd/cmd/drive/get_job_config_test.go | 2 +- runner-manager/cfd/cmd/drive/stage.go | 2 +- runner-manager/cfd/cmd/root.go | 2 +- runner-manager/cfd/main.go | 2 +- 10 files changed, 10 insertions(+), 15 deletions(-) rename runner-manager/cfd/Makefile => Makefile (100%) rename runner-manager/cfd/go.mod => go.mod (94%) rename runner-manager/cfd/go.sum => go.sum (96%) diff --git a/.github/workflows/cf-driver-go.yml b/.github/workflows/cf-driver-go.yml index 0e98f31..3cad2b8 100644 --- a/.github/workflows/cf-driver-go.yml +++ b/.github/workflows/cf-driver-go.yml @@ -5,10 +5,6 @@ name: "CF Driver: Go Build & Test" on: [pull_request] -defaults: - run: - working-directory: runner-manager/cfd - jobs: build: runs-on: ubuntu-latest @@ -17,12 +13,9 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 - with: - go-version-file: runner-manager/cfd/go.mod - cache-dependency-path: runner-manager/cfd/go.sum - name: Install dependencies - run: go get . + run: go get ./... - name: Check formatting run: test -z "$(gofmt -l .)" diff --git a/runner-manager/cfd/Makefile b/Makefile similarity index 100% rename from runner-manager/cfd/Makefile rename to Makefile diff --git a/runner-manager/cfd/go.mod b/go.mod similarity index 94% rename from runner-manager/cfd/go.mod rename to go.mod index a433e81..2d56d64 100644 --- a/runner-manager/cfd/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/GSA-TTS/gitlab-runner-cloudgov/runner/cfd +module github.com/GSA-TTS/gitlab-runner-cloudgov go 1.23.5 diff --git a/runner-manager/cfd/go.sum b/go.sum similarity index 96% rename from runner-manager/cfd/go.sum rename to go.sum index b25180c..7ada064 100644 --- a/runner-manager/cfd/go.sum +++ b/go.sum @@ -45,5 +45,7 @@ golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go index 7408d1a..6eaffd6 100644 --- a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go +++ b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go @@ -10,7 +10,7 @@ import ( "os" "testing" - cg "github.com/GSA-TTS/gitlab-runner-cloudgov/runner/cfd/cloudgov" + cg "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/cloudgov" "github.com/google/go-cmp/cmp" ) diff --git a/runner-manager/cfd/cmd/drive/get_job_config.go b/runner-manager/cfd/cmd/drive/get_job_config.go index 689c0d6..8a5b672 100644 --- a/runner-manager/cfd/cmd/drive/get_job_config.go +++ b/runner-manager/cfd/cmd/drive/get_job_config.go @@ -9,7 +9,7 @@ import ( "slices" "strings" - "github.com/GSA-TTS/gitlab-runner-cloudgov/runner/cfd/cloudgov" + "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/cloudgov" ) type JobConfig struct { diff --git a/runner-manager/cfd/cmd/drive/get_job_config_test.go b/runner-manager/cfd/cmd/drive/get_job_config_test.go index d2b379b..5b3badb 100644 --- a/runner-manager/cfd/cmd/drive/get_job_config_test.go +++ b/runner-manager/cfd/cmd/drive/get_job_config_test.go @@ -5,7 +5,7 @@ import ( "path/filepath" "testing" - "github.com/GSA-TTS/gitlab-runner-cloudgov/runner/cfd/cloudgov" + "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/cloudgov" "github.com/google/go-cmp/cmp" ) diff --git a/runner-manager/cfd/cmd/drive/stage.go b/runner-manager/cfd/cmd/drive/stage.go index 84ae457..ee211c0 100644 --- a/runner-manager/cfd/cmd/drive/stage.go +++ b/runner-manager/cfd/cmd/drive/stage.go @@ -3,7 +3,7 @@ package drive import ( "fmt" - "github.com/GSA-TTS/gitlab-runner-cloudgov/runner/cfd/cloudgov" + "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/cloudgov" ) type stage struct { diff --git a/runner-manager/cfd/cmd/root.go b/runner-manager/cfd/cmd/root.go index c535dc9..24e6021 100644 --- a/runner-manager/cfd/cmd/root.go +++ b/runner-manager/cfd/cmd/root.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/GSA-TTS/gitlab-runner-cloudgov/runner/cfd/cmd/drive" + "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/cmd/drive" "github.com/spf13/cobra" ) diff --git a/runner-manager/cfd/main.go b/runner-manager/cfd/main.go index 9d780f7..c919d3c 100644 --- a/runner-manager/cfd/main.go +++ b/runner-manager/cfd/main.go @@ -4,7 +4,7 @@ import ( "fmt" "log" - "github.com/GSA-TTS/gitlab-runner-cloudgov/runner/cfd/cmd" + "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/cmd" "github.com/joho/godotenv" ) From 1cd83f4596f09dda1516ea4c7859e352f1fc6539 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Wed, 18 Jun 2025 10:57:09 -0400 Subject: [PATCH 08/27] chore: add a local lazy config file to set test args --- .lazy.lua | 21 +++++++++++++++++++++ runner-manager/cfd/.vscode/launch.json | 11 ----------- 2 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 .lazy.lua delete mode 100644 runner-manager/cfd/.vscode/launch.json diff --git a/.lazy.lua b/.lazy.lua new file mode 100644 index 0000000..4802c03 --- /dev/null +++ b/.lazy.lua @@ -0,0 +1,21 @@ +return { + { + "nvim-neotest/neotest", + config = function() + ---@diagnostic disable-next-line: missing-fields + require("neotest").setup({ + adapters = { + require("neotest-golang")({ + go_test_args = { "-v", "-race", "-count=1", "-tags=integration" }, + go_list_args = { "-tags=integration" }, + dap_go_opts = { + delve = { + build_flags = { "-tags=integration" }, + }, + }, + }), + }, + }) + end, + }, +} diff --git a/runner-manager/cfd/.vscode/launch.json b/runner-manager/cfd/.vscode/launch.json deleted file mode 100644 index ea3fa7e..0000000 --- a/runner-manager/cfd/.vscode/launch.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Debug main.go", - "type": "go", - "request": "launch", - "program": "main.go" - } - ] -} From 8ac16a1ff397d9e29a9b343492d098a650341d5a Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Wed, 18 Jun 2025 11:02:22 -0400 Subject: [PATCH 09/27] tests: use verbose flag for both makefile test options --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 28732e0..539d332 100644 --- a/Makefile +++ b/Makefile @@ -8,10 +8,10 @@ vet: fmt go vet ./... test: vet - go test ./... + go test -v ./... integration: vet - go test -count=1 --tags=integration ./... + go test -v -count=1 --tags=integration ./... build: vet go build From 1456be45b1795e81329aef195fe2f6abef5f82ee Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Wed, 18 Jun 2025 11:02:42 -0400 Subject: [PATCH 10/27] fix: needed to point build at the folder w/ main.go --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 539d332..60bfb5b 100644 --- a/Makefile +++ b/Makefile @@ -14,4 +14,4 @@ integration: vet go test -v -count=1 --tags=integration ./... build: vet - go build + go build ./runner-manager/cfd From 98b9ed3f4895dcbd84a8ec7f6deb16ab189acaea Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Wed, 18 Jun 2025 15:30:05 -0400 Subject: [PATCH 11/27] tests: automate go driver integration test setup/teardown --- runner-manager/cfd/README.md | 14 +-- .../cloudgov/testdata/.cloudgov_creds.sample | 11 --- runner-manager/cfd/sh/integration_setup.sh | 91 +++++++++++++++++++ runner-manager/cfd/sh/integration_teardown.sh | 71 +++++++++++++++ 4 files changed, 170 insertions(+), 17 deletions(-) delete mode 100644 runner-manager/cfd/cloudgov/testdata/.cloudgov_creds.sample create mode 100755 runner-manager/cfd/sh/integration_setup.sh create mode 100755 runner-manager/cfd/sh/integration_teardown.sh diff --git a/runner-manager/cfd/README.md b/runner-manager/cfd/README.md index 9865c90..90f2395 100644 --- a/runner-manager/cfd/README.md +++ b/runner-manager/cfd/README.md @@ -50,12 +50,14 @@ make test ### Integration tests -We only have one integration test right now, and to get it running you'll need to do a bit of local setup. - -1. You will need to first get a username & password for some space on cloud.gov that has at least one app. -1. Then you can add those credentials to `./cg/testdata/.cg_creds` in the style of the `.cg_creds.sample` file there. -1. Run the test with `make integration`, which should give you an error and, in its output, show you what the resulting JSON looks like. -1. Copy that JSON result over to the last line of your `.cg_creds` file and run `make integration` again, this time it should succeed. +Integration tests take a little effort get working. + +1. Set your `cf target` to `sandbox-gsa`. +2. Run `./sh/integration_setup.sh`. + a. This will output credentials and `cf target` info to the `testdata` directories for `./cloudgov` and `./cmd/drive`. + b. It will also create a sample app in your sandbox account to be used for testing. +3. Run integration tests with `make integration` +4. Whenever you're ready, you can clean up the credentials & app made during setup with `./sh/integration_teardown.sh`. ## Builds diff --git a/runner-manager/cfd/cloudgov/testdata/.cloudgov_creds.sample b/runner-manager/cfd/cloudgov/testdata/.cloudgov_creds.sample deleted file mode 100644 index 862cd3f..0000000 --- a/runner-manager/cfd/cloudgov/testdata/.cloudgov_creds.sample +++ /dev/null @@ -1,11 +0,0 @@ -# For simple integration test with cloudgov_integration_test.go -# -# This test will use the credentials provided to run `cf apps` and -# check the output against what you have provided. -# -# 1. copy this file to `.cloudgov_creds` -# 2. replace with real credentials, e.g. a service key's -# 3. replace with real output from `cf apps` -username-1234-asdf -password-asdf-1234 -I am the expected output! diff --git a/runner-manager/cfd/sh/integration_setup.sh b/runner-manager/cfd/sh/integration_setup.sh new file mode 100755 index 0000000..8ac8ba6 --- /dev/null +++ b/runner-manager/cfd/sh/integration_setup.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +usage() { + msg="$1" + status=0 + if [[ -n "$msg" ]]; then + printf "ERROR: %s\n\n" "$msg" >&2 + status=1 + fi + + cat >&2 <<-EOM + Usage: $0 [-ps] BASENAME + + Creates a service account with key and a sample application, outputs to testdata dirs. + + Options: + -p Use api.fr.cloud.gov (defaults to fr-stage) + -s Skip creation, only output creds for BASENAME + EOM + + exit $status +} + +dir=$(dirname "$0") +cd "$dir" + +cf_api="api.fr-stage.cloud.gov" +app_name="cfd_integration_test_AppGet" +skip_create= + +while getopts ":psh" opt; do + case $opt in + p) + cf_api="api.fr.cloud.gov" + ;; + s) + skip_create="true" + ;; + h) + usage + ;; + \?) + usage "unknown option '-$OPTARG'" + ;; + esac +done +shift $((OPTIND - 1)) + +name="$1" + +set -euo pipefail + +# check login +cf spaces &>/dev/null || cf login -a "$cf_api" --sso + +org=$(cf t | grep org | awk '{print $2}') +if [[ $org != 'sandbox-gsa' ]]; then + echo "ERROR: you should really probably use this in your sandbox for now" + exit 1 +fi + +# check name defined, prompt if not +if [[ -z "$name" ]]; then + read -rei "wsr-integration" -p "Please input a basename for the service account: " name +fi + +# create the space deployer, then a key for the deployer +if [[ -z "$skip_create" ]]; then + cf create-service cloud-gov-service-account space-deployer "$name"-deployer + cf create-service-key "$name"-deployer "$name"-key +fi + +# create a teeny app we can use to test client.AppGet +cf push --no-start -k 8M -m 128M -o busybox -u process -c /bin/sh "$app_name" + +out_arr=( + # get the credentials from key and output + "$(cf service-key "$name"-deployer "$name"-key | tail +2 | + jq -r ".credentials | .username,.password")" + # get target org & space + "$(cf t | tail -2 | awk '{print $2}')" + "$app_name" +) + +out_str=$( + IFS=$'\n' + echo "${out_arr[*]}" +) + +echo "$out_str" >../cloudgov/testdata/.cloudgov_creds +echo "$out_str" >../cmd/drive/testdata/.cloudgov_creds diff --git a/runner-manager/cfd/sh/integration_teardown.sh b/runner-manager/cfd/sh/integration_teardown.sh new file mode 100755 index 0000000..63d2fbf --- /dev/null +++ b/runner-manager/cfd/sh/integration_teardown.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +usage() { + msg="$1" + status=0 + if [[ -n "$msg" ]]; then + printf "ERROR: %s\n\n" "$msg" >&2 + status=1 + fi + + cat >&2 <<-EOM + Usage: $0 [-pf] BASENAME + + Deletes testing service account, key and sample application. + + Options: + -p Use api.fr.cloud.gov (defaults to fr-stage) + -f Force deletion without confirmation + EOM + + exit $status +} + +cf_api="api.fr-stage.cloud.gov" +app_name="cfd_integration_test_AppGet" +declare -a args + +while getopts ":pfh" opt; do + case $opt in + p) + cf_api="api.fr.cloud.gov" + ;; + f) + args+=("-f") + ;; + h) + usage + ;; + \?) + usage "unknown option '-$OPTARG'" + ;; + esac +done +shift $((OPTIND - 1)) + +name="$1" + +set -euo pipefail + +# check login +cf spaces &>/dev/null || cf login -a "$cf_api" --sso + +org=$(cf t | grep org | awk '{print $2}') +if [[ $org != 'sandbox-gsa' ]]; then + echo "ERROR: you should really probably use this in your sandbox for now" + exit 1 +fi + +# check name defined, prompt if not +if [[ -z "$name" ]]; then + read -rei "wsr-integration" -p "Please input a basename for the service account: " name +fi + +# delete the teeny app +cf delete -r "${args[@]}" "$app_name" + +# delete the deployer and key +cf delete-service-key "${args[@]}" "$name"-deployer "$name"-key +cf delete-service "${args[@]}" "$name"-deployer + +echo "WARNING: didn't delete any testdata files, you must remove them manually." From c250b163f1b71ffb8f12a578ac1a03521130f07d Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Wed, 18 Jun 2025 15:30:44 -0400 Subject: [PATCH 12/27] chore(cfd): update default cf api to fr-stage --- runner-manager/cfd/cloudgov/cloudgov.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runner-manager/cfd/cloudgov/cloudgov.go b/runner-manager/cfd/cloudgov/cloudgov.go index e9623f2..1956ba7 100644 --- a/runner-manager/cfd/cloudgov/cloudgov.go +++ b/runner-manager/cfd/cloudgov/cloudgov.go @@ -42,7 +42,7 @@ func (e CloudGovClientError) Error() string { } // TODO: we should pull this out of VCAP_APPLICATION -const apiRootURLDefault = "https://api.fr.cloud.gov" +const apiRootURLDefault = "https://api.fr-stage.cloud.gov" func New(i ClientAPI, o *Opts) (*Client, error) { if o == nil { From fe027bb580de9535e2ea0b462c62f48bde971df6 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Wed, 18 Jun 2025 15:31:48 -0400 Subject: [PATCH 13/27] tests(cfd): extract integration setup and improve scanner iteration --- .../cfd/cloudgov/cloudgov_integration_test.go | 119 +++++++----------- .../cfd/internal/tutils/integration_setup.go | 86 +++++++++++++ 2 files changed, 130 insertions(+), 75 deletions(-) create mode 100644 runner-manager/cfd/internal/tutils/integration_setup.go diff --git a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go index 6eaffd6..57b194f 100644 --- a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go +++ b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go @@ -3,94 +3,56 @@ package cloudgov_test import ( - "bufio" - "encoding/json" "errors" - "fmt" - "os" + "regexp" "testing" cg "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/cloudgov" + "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/internal/tutils" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" ) var ( - appGetWanted string - cgClient *cg.Client + cgClient *cg.Client + app, + org, + space string ) -func TestMain(m *testing.M) { - var user, pass string - var err error - - path := "./testdata/.cloudgov_creds" - f, err := os.Open(path) - if err != nil { - fmt.Printf( - "Error opening testdata file = %v\n\033[1;33mDid you forget to create `%v`?\033[0m", - err, path, - ) - os.Exit(1) - } - defer f.Close() - - scanner := bufio.NewScanner(f) - - var i int - var l [3]string - for scanner.Scan() { - text := scanner.Text() - if text[0] == '#' { - continue - } - l[i] = text - if i++; i > 2 { - user, pass, appGetWanted = l[0], l[1], l[2] - break - } - } - - if err = scanner.Err(); err != nil { - fmt.Printf("Error scanning testdata file = %v", err) - return - } - - if user == "" || pass == "" { - fmt.Printf("Could not load variables from testdata") - return - } - - cgClient, err = cg.New(&cg.CFClientAPI{}, &cg.Opts{ - Creds: &cg.Creds{Username: user, Password: pass}, - }) - if err != nil { - fmt.Printf("Error getting cloudgovClient = %v", err) +func setup(t testing.TB) { + t.Helper() + if cgClient != nil { return } + cgClient, org, space, app = tutils.IntegrationSetup(t) +} - m.Run() +func getCmpOpts() cmp.Option { + return cmpopts.IgnoreFields(cg.App{}, "GUID") } func Test_CFAdapter_AppGet(t *testing.T) { - apps, err := cgClient.AppsList() - if err != nil { - t.Errorf("Error running AppsList() = %v", err) - return - } + setup(t) - got, err := json.Marshal(apps) + want := []*cg.App{{ + Name: app, + State: "STOPPED", + }} + + got, err := cgClient.AppsList() if err != nil { - t.Errorf("Error marshalling apps to json = %v", err) - return + t.Fatalf("Error running AppsList() = %v", err) } - if diff := cmp.Diff(string(got), appGetWanted); diff != "" { - t.Errorf("mismatch (-got +want):\n%s", diff) - return + if diff := cmp.Diff(got, want, getCmpOpts()); diff != "" { + t.Fatalf("mismatch (-got +want):\n%s", diff) } } func Test_Push(t *testing.T) { + setup(t) + tests := map[string]struct { want *cg.App wantErr error @@ -100,12 +62,12 @@ func Test_Push(t *testing.T) { wantErr: errors.New("could not find org bad: expected exactly 1 result, but got less or more than 1"), manifest: &cg.AppManifest{Name: "Fail", OrgName: "bad", SpaceName: "bad"}, }, - "Passes with real org and space": { - want: &cg.App{Name: "c0f91804-4d3a-47df-be14-c9eb4fb59324", State: "STARTED"}, + "Passes with sandbox space": { + want: &cg.App{Name: "Test_Push_App", State: "STARTED"}, manifest: &cg.AppManifest{ - OrgName: "gsa-tts-devtools-prototyping", - SpaceName: "cgd-int", - Name: "Some cool app", + OrgName: org, + SpaceName: space, + Name: "Test_Push_App", Docker: cg.AppManifestDocker{ Image: "busybox", }, @@ -120,11 +82,17 @@ func Test_Push(t *testing.T) { t.Run(name, func(t *testing.T) { c := cgClient got, err := c.Push(tt.manifest) + + if got != nil && got.GUID != "" { + tutils.CleanupApp(t, c, got.GUID) + } + if err != nil && (tt.wantErr == nil || err.Error() != tt.wantErr.Error()) { t.Errorf("Client.Push() error = %v, wantErr = %v", err, tt.wantErr) return } - if diff := cmp.Diff(got, tt.want); diff != "" { + + if diff := cmp.Diff(got, tt.want, getCmpOpts()); diff != "" { t.Errorf("mismatch (-got +want):\n%s", diff) } }) @@ -132,14 +100,15 @@ func Test_Push(t *testing.T) { } func Test_SSHCode(t *testing.T) { - c := cgClient - want := "hi" - got, err := c.SSHCode() + setup(t) + got, err := cgClient.SSHCode() if err != nil { t.Errorf("got error = %v", err) return } - if diff := cmp.Diff(got, want); diff != "" { - t.Errorf("mismatch (-got +want):\n%s", diff) + + re := regexp.MustCompile(`[\w-_]{32}`) + if !re.MatchString(got) { + t.Errorf("wanted string matching /%v/, got %v", re, got) } } diff --git a/runner-manager/cfd/internal/tutils/integration_setup.go b/runner-manager/cfd/internal/tutils/integration_setup.go new file mode 100644 index 0000000..d59e471 --- /dev/null +++ b/runner-manager/cfd/internal/tutils/integration_setup.go @@ -0,0 +1,86 @@ +package tutils + +import ( + "bufio" + "fmt" + "os" + "path" + "testing" + + cg "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/cloudgov" +) + +const credPath = "./testdata/.cloudgov_creds" + +func IntegrationSetup(t testing.TB) (client *cg.Client, org string, space string, app string) { + var err error + var user, pass string + + defer (func() { + if err != nil { + t.Fatal(fmt.Errorf("IntegrationSetup: %w", err)) + } + })() + + cwd, err := os.Getwd() + if err != nil { + err = fmt.Errorf("getting cwd: %w", err) + return + } + + f, err := os.Open(path.Join(cwd, credPath)) + if err != nil { + err = fmt.Errorf( + "error opening testdata file = %v\n\033[1;33mDid you forget to create `%v`?\033[0m", + err, credPath, + ) + return + } + defer f.Close() + + vars := []*string{&user, &pass, &org, &space, &app} + scanner := bufio.NewScanner(f) + for scanner.Scan() { + text := scanner.Text() + + // Skipping comments + if text[0] == '#' { + continue + } + + l := len(vars) + if l != 0 { + *vars[0] = text + vars = vars[1:] + } else { + break + } + } + + if err = scanner.Err(); err != nil { + err = fmt.Errorf("scanning testdata file: %w", err) + return + } + + if user == "" || pass == "" { + err = fmt.Errorf("could not load variables from testdata") + return + } + + client, err = cg.New(&cg.CFClientAPI{}, &cg.Opts{ + Creds: &cg.Creds{Username: user, Password: pass}, + }) + if err != nil { + err = fmt.Errorf("getting cloudgovClient: %w", err) + return + } + + return client, org, space, app +} + +func CleanupApp(t testing.TB, c *cg.Client, guid string) { + t.Helper() + if err := c.AppDelete(guid); err != nil { + t.Errorf("failed to delete app: %s", guid) + } +} From 813d267c9e13adafcebb1551f42659e366425124 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Wed, 18 Jun 2025 17:21:37 -0400 Subject: [PATCH 14/27] =?UTF-8?q?feat(cfd):=20WIP=20of=20calling=20SSH=20w?= =?UTF-8?q?/=20milestone=20of=20SSH=20actually=20working=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …and an integration test for it! --- runner-manager/cfd/cmd/drive/stage.go | 56 +++++++++++++++++-- .../cfd/cmd/drive/stage_integration_test.go | 49 ++++++++++++++++ 2 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 runner-manager/cfd/cmd/drive/stage_integration_test.go diff --git a/runner-manager/cfd/cmd/drive/stage.go b/runner-manager/cfd/cmd/drive/stage.go index ee211c0..b0153aa 100644 --- a/runner-manager/cfd/cmd/drive/stage.go +++ b/runner-manager/cfd/cmd/drive/stage.go @@ -2,6 +2,7 @@ package drive import ( "fmt" + "os/exec" "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/cloudgov" ) @@ -16,20 +17,28 @@ type stage struct { } type commonStage struct { + *stage client *cloudgov.Client config *JobConfig } -func newStage() (s *stage, err error) { +func newStage(client *cloudgov.Client) (s *stage, err error) { defer func() { if err != nil { err = fmt.Errorf("error creating stage: %w", err) } }() - s.common.client, err = cloudgov.New(&cloudgov.CFClientAPI{}, nil) - if err != nil { - return + s = &stage{} + s.common.stage = s + + if client != nil { + s.common.client = client + } else { + s.common.client, err = cloudgov.New(&cloudgov.CFClientAPI{}, nil) + if err != nil { + return + } } s.common.config, err = getJobConfig() @@ -44,3 +53,42 @@ func newStage() (s *stage, err error) { return } + +func (s *stage) RunSSH(guid string, cmd string) error { + // cfg := s.common.config.EgressProxyConfig + + pass, err := s.common.client.SSHCode() + if err != nil { + return err + } + + // sshCmd := exec.Command( + // "sshpass", "-p", pass, + // "ssh -p 2222 -T", + // "-o 'StrictHostKeyChecking=no'", + // fmt.Sprintf("-o 'ProxyCommand corkscrew %v %v %%h %%p %v'", + // cfg.ProxyHostSSH, cfg.ProxyPortSSH, cfg.ProxyAuthFile, + // ), + // fmt.Sprintf("cf:%s/0@ssh.fr.cloud.gov", guid), + // "cmd", + // ) + + host := fmt.Sprintf("cf:%s/0@ssh.fr-stage.cloud.gov", guid) + sshCmd := exec.Command( + "sshpass", "-p", pass, + "ssh", "-p 2222", "-T", "-o StrictHostKeyChecking=no", host, + cmd, + ) + + // fmt.Println(sshCmd.String()) + // fmt.Print(strings.Join(sshCmd.Environ(), "\n")) + + out, err := sshCmd.Output() + if err != nil { + return err + } + + fmt.Print(string(out)) + + return nil +} diff --git a/runner-manager/cfd/cmd/drive/stage_integration_test.go b/runner-manager/cfd/cmd/drive/stage_integration_test.go new file mode 100644 index 0000000..e0cc01a --- /dev/null +++ b/runner-manager/cfd/cmd/drive/stage_integration_test.go @@ -0,0 +1,49 @@ +//go:build integration + +package drive + +import ( + "os/exec" + "testing" + + "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/cloudgov" + "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/internal/tutils" +) + +var ( + cgClient *cloudgov.Client + app, + org, + space string +) + +func setup(t testing.TB) { + t.Helper() + if cgClient != nil { + return + } + cgClient, org, space, app = tutils.IntegrationSetup(t) +} + +func Test_RunSSH(t *testing.T) { + setup(t) + + stage, err := newStage(cgClient) + if err != nil { + t.Fatal(err) + } + + apps, err := cgClient.AppsList() + if err != nil { + t.Fatal(err) + } + + err = stage.RunSSH(apps[0].GUID, "echo $VCAP_APPLICATION") + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + t.Fatal(string(exitErr.Stderr)) + } else { + t.Fatal(err) + } + } +} From 8cb0d1aa87298d7048e985c08a8bcdda731eea53 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Mon, 23 Jun 2025 10:42:27 -0400 Subject: [PATCH 15/27] refactor(cfd): integration setup/teardown easier to use --- runner-manager/cfd/sh/integration_setup.sh | 41 +++++++++---------- runner-manager/cfd/sh/integration_teardown.sh | 30 +++++++------- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/runner-manager/cfd/sh/integration_setup.sh b/runner-manager/cfd/sh/integration_setup.sh index 8ac8ba6..a39b009 100755 --- a/runner-manager/cfd/sh/integration_setup.sh +++ b/runner-manager/cfd/sh/integration_setup.sh @@ -1,5 +1,12 @@ #!/usr/bin/env bash +cf_api="api.fr-stage.cloud.gov" +cf_api_prod="api.fr.cloud.gov" +skip_create= + +basename="wsr-integration" +app_name="cfd_integration_test_AppGet" + usage() { msg="$1" status=0 @@ -9,13 +16,14 @@ usage() { fi cat >&2 <<-EOM - Usage: $0 [-ps] BASENAME + Usage: $0 [-bps] Creates a service account with key and a sample application, outputs to testdata dirs. Options: - -p Use api.fr.cloud.gov (defaults to fr-stage) - -s Skip creation, only output creds for BASENAME + -b Basename to use for service account & key (defaults to $basename) + -p Use $cf_api_prod (defaults to $cf_api) + -s Skip creation and only get & output credentials EOM exit $status @@ -24,14 +32,13 @@ usage() { dir=$(dirname "$0") cd "$dir" -cf_api="api.fr-stage.cloud.gov" -app_name="cfd_integration_test_AppGet" -skip_create= - -while getopts ":psh" opt; do +while getopts ":b:psh" opt; do case $opt in + b) + basename="$OPTARG" + ;; p) - cf_api="api.fr.cloud.gov" + cf_api="$cf_api_prod" ;; s) skip_create="true" @@ -44,9 +51,6 @@ while getopts ":psh" opt; do ;; esac done -shift $((OPTIND - 1)) - -name="$1" set -euo pipefail @@ -59,23 +63,18 @@ if [[ $org != 'sandbox-gsa' ]]; then exit 1 fi -# check name defined, prompt if not -if [[ -z "$name" ]]; then - read -rei "wsr-integration" -p "Please input a basename for the service account: " name -fi - # create the space deployer, then a key for the deployer if [[ -z "$skip_create" ]]; then - cf create-service cloud-gov-service-account space-deployer "$name"-deployer - cf create-service-key "$name"-deployer "$name"-key + cf create-service cloud-gov-service-account space-deployer "$basename"-deployer + cf create-service-key "$basename"-deployer "$basename"-key fi # create a teeny app we can use to test client.AppGet -cf push --no-start -k 8M -m 128M -o busybox -u process -c /bin/sh "$app_name" +cf push -k 8M -m 128M -o busybox -u process -c /bin/sh "$app_name" out_arr=( # get the credentials from key and output - "$(cf service-key "$name"-deployer "$name"-key | tail +2 | + "$(cf service-key "$basename"-deployer "$basename"-key | tail +2 | jq -r ".credentials | .username,.password")" # get target org & space "$(cf t | tail -2 | awk '{print $2}')" diff --git a/runner-manager/cfd/sh/integration_teardown.sh b/runner-manager/cfd/sh/integration_teardown.sh index 63d2fbf..b6eded9 100755 --- a/runner-manager/cfd/sh/integration_teardown.sh +++ b/runner-manager/cfd/sh/integration_teardown.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +cf_api="api.fr-stage.cloud.gov" +cf_api_prod="api.fr.cloud.gov" + +basename="wsr-integration" +app_name="cfd_integration_test_AppGet" + usage() { msg="$1" status=0 @@ -9,24 +15,26 @@ usage() { fi cat >&2 <<-EOM - Usage: $0 [-pf] BASENAME + Usage: $0 [-bpf] Deletes testing service account, key and sample application. Options: - -p Use api.fr.cloud.gov (defaults to fr-stage) + -b Basename to use for service account & key (defaults to $basename) + -p Use $cf_api_prod (defaults to $cf_api) -f Force deletion without confirmation EOM exit $status } -cf_api="api.fr-stage.cloud.gov" -app_name="cfd_integration_test_AppGet" declare -a args -while getopts ":pfh" opt; do +while getopts ":b:pfh" opt; do case $opt in + b) + basename="$OPTARG" + ;; p) cf_api="api.fr.cloud.gov" ;; @@ -41,9 +49,6 @@ while getopts ":pfh" opt; do ;; esac done -shift $((OPTIND - 1)) - -name="$1" set -euo pipefail @@ -56,16 +61,11 @@ if [[ $org != 'sandbox-gsa' ]]; then exit 1 fi -# check name defined, prompt if not -if [[ -z "$name" ]]; then - read -rei "wsr-integration" -p "Please input a basename for the service account: " name -fi - # delete the teeny app cf delete -r "${args[@]}" "$app_name" # delete the deployer and key -cf delete-service-key "${args[@]}" "$name"-deployer "$name"-key -cf delete-service "${args[@]}" "$name"-deployer +cf delete-service-key "${args[@]}" "$basename"-deployer "$basename"-key +cf delete-service "${args[@]}" "$basename"-deployer echo "WARNING: didn't delete any testdata files, you must remove them manually." From ed745aedbd9ea960dfd930c31323b03b1b581110 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Mon, 23 Jun 2025 10:43:55 -0400 Subject: [PATCH 16/27] fix(cfd): change AppGet integration to use STARTED state for consistency --- runner-manager/cfd/cloudgov/cloudgov_integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go index 57b194f..2c29b04 100644 --- a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go +++ b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go @@ -37,7 +37,7 @@ func Test_CFAdapter_AppGet(t *testing.T) { want := []*cg.App{{ Name: app, - State: "STOPPED", + State: "STARTED", }} got, err := cgClient.AppsList() From 0e5465086cfc0fd27390a592c549d77e991f4936 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Mon, 23 Jun 2025 10:53:29 -0400 Subject: [PATCH 17/27] fix(cfd): prepare run cmd needed nil passed to newStage --- runner-manager/cfd/cmd/drive/prepare.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runner-manager/cfd/cmd/drive/prepare.go b/runner-manager/cfd/cmd/drive/prepare.go index fc32c8c..4fbd8ab 100644 --- a/runner-manager/cfd/cmd/drive/prepare.go +++ b/runner-manager/cfd/cmd/drive/prepare.go @@ -34,7 +34,7 @@ https://docs.gitlab.com/runner/executors/custom.html#prepare`, type prepStage commonStage func run(cmd *cobra.Command, args []string) error { - s, err := newStage() + s, err := newStage(nil) if err != nil { return fmt.Errorf("error initializing prepare stage: %w", err) } From 7eae1ec8f45bade68aa89ee79915412526ff87ef Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Mon, 23 Jun 2025 11:42:01 -0400 Subject: [PATCH 18/27] feat(cfd) RunSSH to use proxy config when available --- runner-manager/cfd/cmd/drive/stage.go | 34 ++++++++++----------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/runner-manager/cfd/cmd/drive/stage.go b/runner-manager/cfd/cmd/drive/stage.go index b0153aa..af9995a 100644 --- a/runner-manager/cfd/cmd/drive/stage.go +++ b/runner-manager/cfd/cmd/drive/stage.go @@ -3,6 +3,7 @@ package drive import ( "fmt" "os/exec" + "strings" "github.com/GSA-TTS/gitlab-runner-cloudgov/runner-manager/cfd/cloudgov" ) @@ -55,33 +56,24 @@ func newStage(client *cloudgov.Client) (s *stage, err error) { } func (s *stage) RunSSH(guid string, cmd string) error { - // cfg := s.common.config.EgressProxyConfig - pass, err := s.common.client.SSHCode() if err != nil { return err } - // sshCmd := exec.Command( - // "sshpass", "-p", pass, - // "ssh -p 2222 -T", - // "-o 'StrictHostKeyChecking=no'", - // fmt.Sprintf("-o 'ProxyCommand corkscrew %v %v %%h %%p %v'", - // cfg.ProxyHostSSH, cfg.ProxyPortSSH, cfg.ProxyAuthFile, - // ), - // fmt.Sprintf("cf:%s/0@ssh.fr.cloud.gov", guid), - // "cmd", - // ) - + args := []string{"ssh", "-p 2222", "-T", "-o StrictHostKeyChecking=no"} host := fmt.Sprintf("cf:%s/0@ssh.fr-stage.cloud.gov", guid) - sshCmd := exec.Command( - "sshpass", "-p", pass, - "ssh", "-p 2222", "-T", "-o StrictHostKeyChecking=no", host, - cmd, - ) - - // fmt.Println(sshCmd.String()) - // fmt.Print(strings.Join(sshCmd.Environ(), "\n")) + + epCfg := s.common.config.EgressProxyConfig + if epCfg != (EgressProxyConfig{}) { + proxy := fmt.Sprintf("-o ProxyCommand corkscrew %v %v %%h %%p %v", + epCfg.ProxyHostSSH, epCfg.ProxyPortSSH, epCfg.ProxyAuthFile, + ) + args = append(args, proxy) + } + + sshCmd := exec.Command("sshpass", append(args, host)...) + sshCmd.Stdin = strings.NewReader(pass) // give pass to sshpass through stdin out, err := sshCmd.Output() if err != nil { From a8a9ae3c50d7ee3dc2da6635ad4d49dde478319e Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Mon, 23 Jun 2025 16:25:18 -0400 Subject: [PATCH 19/27] feat(cfd) add route mapping functionality & integration test --- runner-manager/cfd/cloudgov/cf_client.go | 24 ++++++- runner-manager/cfd/cloudgov/cloudgov.go | 26 +++++--- .../cfd/cloudgov/cloudgov_integration_test.go | 65 +++++++++++++++++++ runner-manager/cfd/sh/integration_setup.sh | 2 +- 4 files changed, 105 insertions(+), 12 deletions(-) diff --git a/runner-manager/cfd/cloudgov/cf_client.go b/runner-manager/cfd/cloudgov/cf_client.go index 971dc1e..d9ab117 100644 --- a/runner-manager/cfd/cloudgov/cf_client.go +++ b/runner-manager/cfd/cloudgov/cf_client.go @@ -73,7 +73,7 @@ func castApp(app *resource.App) *App { if app == nil || app.GUID == "" { return nil } - return &(App{Name: app.Name, GUID: app.GUID, State: app.State}) + return &(App{Name: app.Name, GUID: app.GUID, State: app.State, SpaceGUID: app.Relationships.Space.Data.GUID}) } func castApps(apps []*resource.App) []*App { @@ -109,3 +109,25 @@ func (cf *CFClientAPI) sshCode() (string, error) { ctx := context.Background() return cf.conn().SSHCode(ctx) } + +func (cf *CFClientAPI) mapRoute( + ctx context.Context, + app *App, + domain string, space string, host string, path string, port int, +) error { + opts := resource.NewRouteCreateWithHost(domain, space, host, path, port) + + route, err := cf.conn().Routes.Create(ctx, opts) + if err != nil { + return err + } + + _, err = cf.conn().Routes.InsertDestinations( + ctx, + route.GUID, + []*resource.RouteDestinationInsertOrReplace{{ + App: resource.RouteDestinationApp{GUID: &app.GUID}, + }}, + ) + return err +} diff --git a/runner-manager/cfd/cloudgov/cloudgov.go b/runner-manager/cfd/cloudgov/cloudgov.go index 1956ba7..0253792 100644 --- a/runner-manager/cfd/cloudgov/cloudgov.go +++ b/runner-manager/cfd/cloudgov/cloudgov.go @@ -1,11 +1,7 @@ package cloudgov -// Stuff we'll need to implement, for ref -// -// mapRoute() -// -// addNetworkPolicy() -// removeNetworkPolicy() +import "context" + type ClientAPI interface { connect(url string, creds *Creds) error @@ -15,6 +11,8 @@ type ClientAPI interface { appsList() (apps []*App, err error) sshCode() (string, error) + mapRoute(ctx context.Context, app *App, domain string, space string, host string, path string, port int) error + addNetworkPolicy(app *App, dest string, space string, port string) error } type CredsGetter interface { @@ -42,7 +40,10 @@ func (e CloudGovClientError) Error() string { } // TODO: we should pull this out of VCAP_APPLICATION -const apiRootURLDefault = "https://api.fr-stage.cloud.gov" +const ( + apiRootURLDefault = "https://api.fr-stage.cloud.gov" + internalDomainGUID = "8a5d6a8c-cfc1-4fc4-afc9-aa563ff9df5e" +) func New(i ClientAPI, o *Opts) (*Client, error) { if o == nil { @@ -78,9 +79,10 @@ func (c *Client) Connect() (*Client, error) { } type App struct { - Name string - GUID string - State string + Name string + GUID string + State string + SpaceGUID string } func (c *Client) AppGet(id string) (*App, error) { @@ -133,3 +135,7 @@ func (c *Client) ServicesPush(manifests []*AppManifest) ([]*App, error) { func (c *Client) SSHCode() (string, error) { return c.sshCode() } + +func (c *Client) MapServiceRoute(app *App) error { + return c.mapRoute(context.Background(), app, internalDomainGUID, app.SpaceGUID, app.Name, "", 0) +} diff --git a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go index 2c29b04..46ea48f 100644 --- a/runner-manager/cfd/cloudgov/cloudgov_integration_test.go +++ b/runner-manager/cfd/cloudgov/cloudgov_integration_test.go @@ -3,7 +3,10 @@ package cloudgov_test import ( + "encoding/json" "errors" + "fmt" + "os/exec" "regexp" "testing" @@ -112,3 +115,65 @@ func Test_SSHCode(t *testing.T) { t.Errorf("wanted string matching /%v/, got %v", re, got) } } + +func cleanupRoute(t testing.TB, app *cg.App) error { + t.Helper() + + delRouteCmd := exec.Command( + "cf", "delete-route", "-f", "apps.internal", + fmt.Sprintf("-n%s", app.Name), + ) + + out, err := delRouteCmd.CombinedOutput() + if err != nil { + t.Log(string(out)) + if exErr, ok := err.(*exec.ExitError); ok { + t.Log(exErr.Error()) + t.Fatal(string(exErr.Stderr)) + } else { + t.Fatal(err) + } + } + + return err +} + +func TestClient_MapServiceRoute(t *testing.T) { + setup(t) + + apps, err := cgClient.AppsList() + if err != nil { + t.Fatal(err) + } + app := apps[0] + + err = cgClient.MapServiceRoute(app) + defer cleanupRoute(t, app) + if err != nil { + t.Fatal(err) + } + + ckRouteCmd := exec.Command("cf", "curl", fmt.Sprintf("/v3/apps/%s/routes", app.GUID)) + out, err := ckRouteCmd.CombinedOutput() + if err != nil { + t.Log(out) + t.Fatal(err) + } + + var routeOut map[string][]map[string]string + if err := json.Unmarshal(out, &routeOut); err != nil { + t.Log("partial unmarshalling error expected…") + t.Log(err) + } + + wantUrl := fmt.Sprintf("%s.apps.internal", app.Name) + + for _, m := range routeOut["resources"] { + if m["host"] == app.Name && m["url"] == wantUrl { + return + } + } + + t.Logf("%#v", routeOut["resources"]) + t.Fatalf("could not find route with %s host and correct url", app.Name) +} diff --git a/runner-manager/cfd/sh/integration_setup.sh b/runner-manager/cfd/sh/integration_setup.sh index a39b009..f0c674a 100755 --- a/runner-manager/cfd/sh/integration_setup.sh +++ b/runner-manager/cfd/sh/integration_setup.sh @@ -70,7 +70,7 @@ if [[ -z "$skip_create" ]]; then fi # create a teeny app we can use to test client.AppGet -cf push -k 8M -m 128M -o busybox -u process -c /bin/sh "$app_name" +cf push --no-route -k 8M -m 128M -o busybox -u process -c /bin/sh "$app_name" out_arr=( # get the credentials from key and output From 6878ae272467825301d0d44ae584f53ed1edb726 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Mon, 23 Jun 2025 16:25:46 -0400 Subject: [PATCH 20/27] chore(cfd) add function stubs for unimplemented prepare steps --- runner-manager/cfd/cloudgov/cf_client.go | 5 +++++ runner-manager/cfd/cmd/drive/prepare.go | 24 ++++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/runner-manager/cfd/cloudgov/cf_client.go b/runner-manager/cfd/cloudgov/cf_client.go index d9ab117..0f0b838 100644 --- a/runner-manager/cfd/cloudgov/cf_client.go +++ b/runner-manager/cfd/cloudgov/cf_client.go @@ -131,3 +131,8 @@ func (cf *CFClientAPI) mapRoute( ) return err } + +// addNetworkPolicy implements ClientAPI. +func (cf *CFClientAPI) addNetworkPolicy(app *App, dest string, space string, port string) error { + panic("unimplemented") +} diff --git a/runner-manager/cfd/cmd/drive/prepare.go b/runner-manager/cfd/cmd/drive/prepare.go index 4fbd8ab..ccb6220 100644 --- a/runner-manager/cfd/cmd/drive/prepare.go +++ b/runner-manager/cfd/cmd/drive/prepare.go @@ -60,11 +60,12 @@ func (s *prepStage) exec() (err error) { return err } - // TODO: - // install deps - // allow access to services + err = s.installDeps() + if err != nil { + return err + } - return err + return s.setNetworkPolicies() } // TODO: refactor to include a service manifests slice and @@ -77,10 +78,21 @@ func (s *prepStage) startServices() error { for _, serv := range s.config.Services { s.client.Push(serv.Manifest) // map-route containerID apps.internal --hostname containerID - // + + // TODO: implement WSR_ vars + // Leaving this until more is implemented so the form can fit function // export WSR_SERVICE_HOST_$alias=$containerID.apps.internal - // } return nil } + +// TODO: implement +func (s *prepStage) installDeps() error { + panic("unimplemented") +} + +// TODO: implement +func (s *prepStage) setNetworkPolicies() error { + panic("unimplemented") +} From 17af898918800ec7cef50f348d783b1e7c882cc7 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Fri, 27 Jun 2025 14:52:06 -0400 Subject: [PATCH 21/27] chore: removing unused ssh_proxy script --- runner-manager/cfd/sh/ssh_proxy.sh | 94 ------------------------------ 1 file changed, 94 deletions(-) delete mode 100755 runner-manager/cfd/sh/ssh_proxy.sh diff --git a/runner-manager/cfd/sh/ssh_proxy.sh b/runner-manager/cfd/sh/ssh_proxy.sh deleted file mode 100755 index d0ccfa7..0000000 --- a/runner-manager/cfd/sh/ssh_proxy.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env bash - -usage=" -$0: Run SSH through egress-proxy with corkscrew. Must be provided password with either -e, -p, or STDIN. - -Usage: - $0 -h - $0 -g -H -P -F [-p |-e] - -Options: --h show help and exit --g GUID of the app to connect with --H egress proxy host --P egress proxy port --F egress proxy basic auth credential file -[-p] ssh password -[-e] take password from env var 'SSHPASS' - command to pass to ssh as-is -" - -function exitWithUsage() { - echo "$usage" - exit "$1" -} - -guid="" -proxyHost="" -proxyPort="" -proxyFile="" -sshPass="" - -set -e -while getopts ":hg:H:P:F:p:e" opt; do - case "${opt}" in - g) - guid=${OPTARG} - ;; - H) - proxyHost=${OPTARG} - ;; - P) - proxyPort=${OPTARG} - ;; - F) - proxyFile=${OPTARG} - ;; - p) - sshPass=${OPTARG} - ;; - e) - sshPass=${SSHPASS:?"-e used but SSHPASS undefined, run with -h for usage"} - ;; - h | *) - exitWithUsage - ;; - esac -done -shift $((OPTIND - 1)) - -# read sshPass from stdin -if [[ -z "$sshPass" ]]; then - sshPass=$(cat) -fi - -# still no sshPass, exiting -if [[ -z "$sshPass" ]]; then - echo "error: ssh password is required but none passed with -p, -e or STDIN" - exitWithUsage 1 -fi - -if [[ -z "$guid" ]]; then - echo "error: -g is required" - exitWithUsage 1 -fi - -if [[ -z "$proxyHost" ]]; then - echo "error: -H is required" - exitWithUsage 1 -fi - -if [[ -z "$proxyPort" ]]; then - echo "error: -P is required" - exitWithUsage 1 -fi - -if [[ -z "$proxyFile" ]]; then - echo "error: -F is required" - exitWithUsage 1 -fi - -SSHPASS=$sshPass sshpass -e ssh -p 2222 -T \ - -o "StrictHostKeyChecking=no" \ - -o "ProxyCommand corkscrew $proxyHost $proxyPort %h %p $proxyFile" \ - cf:"$guid"/0@ssh.fr.cloud.gov "$@" From 3331f338394fab8b8b13bac5433c1500e6e20590 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Fri, 27 Jun 2025 15:09:01 -0400 Subject: [PATCH 22/27] fix(cfd): teardown not using cf_api_prod var for -p --- runner-manager/cfd/sh/integration_teardown.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runner-manager/cfd/sh/integration_teardown.sh b/runner-manager/cfd/sh/integration_teardown.sh index b6eded9..924c926 100755 --- a/runner-manager/cfd/sh/integration_teardown.sh +++ b/runner-manager/cfd/sh/integration_teardown.sh @@ -36,7 +36,7 @@ while getopts ":b:pfh" opt; do basename="$OPTARG" ;; p) - cf_api="api.fr.cloud.gov" + cf_api="$cf_api_prod" ;; f) args+=("-f") From 6b8954fc45eda9d4f5266cdfa2669767b6f22882 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Thu, 10 Jul 2025 17:29:30 -0400 Subject: [PATCH 23/27] chore(cfd): more obviously fake (and consistent) egress creds in test --- runner-manager/cfd/cmd/drive/get_job_config_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/runner-manager/cfd/cmd/drive/get_job_config_test.go b/runner-manager/cfd/cmd/drive/get_job_config_test.go index 5b3badb..ded8bae 100644 --- a/runner-manager/cfd/cmd/drive/get_job_config_test.go +++ b/runner-manager/cfd/cmd/drive/get_job_config_test.go @@ -134,7 +134,7 @@ func Test_parseVcapAppJSON(t *testing.T) { } func Test_parseVcapServicesJSON(t *testing.T) { - sample := `{"s3":[{"label":"s3","provider":null,"plan":"basic-sandbox","name":"glr-dependency-cache","tags":["AWS","S3","object-storage","terraform-cloudgov-managed"],"instance_guid":"d1541026","instance_name":"glr-dependency-cache","binding_guid":"9f316c56","binding_name":null,"credentials":{"uri":"s3://goooo:booo@s3-fips.us-gov-west-1.aaws.com/cg-d1541026","insecure_skip_verify":false,"access_key_id":"jjjjj","secret_access_key":"ssssssss","region":"us-gov-west-1","bucket":"cg-d1541026","endpoint":"s3-fips.us-gov-west-1.amazonaws.com","fips_endpoint":"s3-fips.us-gov-west-1.amazonaws.com","additional_buckets":[]},"syslog_drain_url":null,"volume_mounts":[]}],"user-provided":[{"label":"user-provided","name":"glr-egress-proxy-credentials","tags":[],"instance_guid":"608e3f73","instance_name":"glr-egress-proxy-credentials","binding_guid":"7530ea7b","binding_name":null,"credentials":{"cred_string":"018052ba:ukgZ","domain":"egress-proxy.apps.internal","http_port":8080,"http_uri":"http://018052b:ukHK@egress-proxy.apps.internal:8080","https_uri":"https://018052ba:ukHK1@egress-proxy.apps.internal:61443"},"syslog_drain_url":null,"volume_mounts":[]}]}` + sample := `{"s3":[{"label":"s3","provider":null,"plan":"basic-sandbox","name":"glr-dependency-cache","tags":["AWS","S3","object-storage","terraform-cloudgov-managed"],"instance_guid":"d1541026","instance_name":"glr-dependency-cache","binding_guid":"9f316c56","binding_name":null,"credentials":{"uri":"s3://goooo:booo@s3-fips.us-gov-west-1.aaws.com/cg-d1541026","insecure_skip_verify":false,"access_key_id":"jjjjj","secret_access_key":"ssssssss","region":"us-gov-west-1","bucket":"cg-d1541026","endpoint":"s3-fips.us-gov-west-1.amazonaws.com","fips_endpoint":"s3-fips.us-gov-west-1.amazonaws.com","additional_buckets":[]},"syslog_drain_url":null,"volume_mounts":[]}],"user-provided":[{"label":"user-provided","name":"glr-egress-proxy-credentials","tags":[],"instance_guid":"608e3f73","instance_name":"glr-egress-proxy-credentials","binding_guid":"7530ea7b","binding_name":null,"credentials":{"cred_string":"bingo:dingo","domain":"egress-proxy.apps.internal","http_port":8080,"http_uri":"http://bingo:dingo@egress-proxy.apps.internal:8080","https_uri":"https://bingo:dingo@egress-proxy.apps.internal:61443"},"syslog_drain_url":null,"volume_mounts":[]}]}` t.Setenv("VCAP_SERVICES", sample) t.Setenv("PROXY_CREDENTIAL_INSTANCE", "glr-egress-proxy-credentials") @@ -146,7 +146,7 @@ func Test_parseVcapServicesJSON(t *testing.T) { authFile := filepath.Join(dir, "ssh_proxy.auth") t.Setenv("PROXY_AUTH_FILE", authFile) - credStringWanted := "018052ba:ukgZ" + credStringWanted := "bingo:dingo" wantedServices := VcapServicesData{ "s3": []VcapServiceInstance{{ @@ -157,16 +157,16 @@ func Test_parseVcapServicesJSON(t *testing.T) { Credentials: VcapServiceCredentials{ Domain: "egress-proxy.apps.internal", HTTPPort: 8080, - HTTPURI: "http://018052b:ukHK@egress-proxy.apps.internal:8080", - HTTPSURI: "https://018052ba:ukHK1@egress-proxy.apps.internal:61443", + HTTPURI: "http://bingo:dingo@egress-proxy.apps.internal:8080", + HTTPSURI: "https://bingo:dingo@egress-proxy.apps.internal:61443", CredString: credStringWanted, }, }}, } wantedEgressConfig := EgressProxyConfig{ - ProxyHostHTTP: "http://018052b:ukHK@egress-proxy.apps.internal:8080", - ProxyHostHTTPS: "https://018052ba:ukHK1@egress-proxy.apps.internal:61443", + ProxyHostHTTP: "http://bingo:dingo@egress-proxy.apps.internal:8080", + ProxyHostHTTPS: "https://bingo:dingo@egress-proxy.apps.internal:61443", ProxyHostSSH: "egress-proxy.apps.internal", ProxyPortSSH: 8080, ProxyAuthFile: authFile, From c6ddfe848d33acc9561a39653d3453ce695d10ab Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Fri, 11 Jul 2025 09:52:04 -0400 Subject: [PATCH 24/27] style(cfd): staticcheck wants uppercase --- runner-manager/cfd/cmd/drive/get_job_config.go | 2 +- runner-manager/cfd/cmd/drive/get_job_config_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/runner-manager/cfd/cmd/drive/get_job_config.go b/runner-manager/cfd/cmd/drive/get_job_config.go index 8a5b672..c3a6ff4 100644 --- a/runner-manager/cfd/cmd/drive/get_job_config.go +++ b/runner-manager/cfd/cmd/drive/get_job_config.go @@ -72,7 +72,7 @@ type VcapAppData struct { CFApi string `json:"cf_api"` OrgID string `json:"org_id"` OrgName string `json:"organization_name"` - SpaceId string `json:"space_id"` + SpaceID string `json:"space_id"` SpaceName string `json:"space_name"` } diff --git a/runner-manager/cfd/cmd/drive/get_job_config_test.go b/runner-manager/cfd/cmd/drive/get_job_config_test.go index ded8bae..7a71ecb 100644 --- a/runner-manager/cfd/cmd/drive/get_job_config_test.go +++ b/runner-manager/cfd/cmd/drive/get_job_config_test.go @@ -118,7 +118,7 @@ func Test_parseVcapAppJSON(t *testing.T) { wanted := VcapAppData{ CFApi: "https://api.fr.cloud.gov", OrgName: "gsa-tts-devtools-prototyping", - SpaceId: "8969a4b6-01aa-431d-9790-77cc4c47e3e7", + SpaceID: "8969a4b6-01aa-431d-9790-77cc4c47e3e7", SpaceName: "zjr-gl-test", } From ccdacde7b70b0dc6494fcf5a72ea891aa5bc5440 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Fri, 11 Jul 2025 09:52:21 -0400 Subject: [PATCH 25/27] chore(cfd): add a sample vcap_application json --- .../drive/testdata/sample_vcap_application.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 runner-manager/cfd/cmd/drive/testdata/sample_vcap_application.json diff --git a/runner-manager/cfd/cmd/drive/testdata/sample_vcap_application.json b/runner-manager/cfd/cmd/drive/testdata/sample_vcap_application.json new file mode 100644 index 0000000..757c5e9 --- /dev/null +++ b/runner-manager/cfd/cmd/drive/testdata/sample_vcap_application.json @@ -0,0 +1,17 @@ +{ + "cf_api": "https://api.fr-stage.cloud.gov", + "limits": { "fds": 16384, "mem": 128, "disk": 8 }, + "application_name": "cfd_integration_test_AppGet_2", + "application_uris": [], + "name": "cfd_integration_test_AppGet_2", + "space_name": "zachary.rollyson", + "space_id": "a9ec0873-e8a5-4d39-99d2-4a934f0ea23d", + "organization_id": "da8e238d-c24a-4528-95db-ae11e76bb8f1", + "organization_name": "sandbox-gsa", + "uris": [], + "process_id": "5d5bea40-e3ba-49a6-b46a-1645b0eac86b", + "process_type": "web", + "application_id": "5d5bea40-e3ba-49a6-b46a-1645b0eac86b", + "version": "141686d8-4b7a-4f27-8cde-8d698c3ef750", + "application_version": "141686d8-4b7a-4f27-8cde-8d698c3ef750" +} From 71db28268ed191f1ab1b49751ec770443f618d44 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Fri, 11 Jul 2025 10:18:25 -0400 Subject: [PATCH 26/27] feat(cfd): pull cf api url out of vcap_application --- runner-manager/cfd/cloudgov/cloudgov.go | 1 - runner-manager/cfd/cmd/drive/stage.go | 15 +++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/runner-manager/cfd/cloudgov/cloudgov.go b/runner-manager/cfd/cloudgov/cloudgov.go index 0253792..b4b4f75 100644 --- a/runner-manager/cfd/cloudgov/cloudgov.go +++ b/runner-manager/cfd/cloudgov/cloudgov.go @@ -39,7 +39,6 @@ func (e CloudGovClientError) Error() string { return e.msg } -// TODO: we should pull this out of VCAP_APPLICATION const ( apiRootURLDefault = "https://api.fr-stage.cloud.gov" internalDomainGUID = "8a5d6a8c-cfc1-4fc4-afc9-aa563ff9df5e" diff --git a/runner-manager/cfd/cmd/drive/stage.go b/runner-manager/cfd/cmd/drive/stage.go index af9995a..f942d9a 100644 --- a/runner-manager/cfd/cmd/drive/stage.go +++ b/runner-manager/cfd/cmd/drive/stage.go @@ -33,20 +33,23 @@ func newStage(client *cloudgov.Client) (s *stage, err error) { s = &stage{} s.common.stage = s + s.common.config, err = getJobConfig() + if err != nil { + return + } + if client != nil { s.common.client = client } else { - s.common.client, err = cloudgov.New(&cloudgov.CFClientAPI{}, nil) + s.common.client, err = cloudgov.New( + &cloudgov.CFClientAPI{}, + &cloudgov.Opts{APIRootURL: s.common.config.CFApi}, + ) if err != nil { return } } - s.common.config, err = getJobConfig() - if err != nil { - return - } - // conf s.prep = (*prepStage)(&s.common) // run From cc91626fbf9734178bc7221efbd117792abb11b9 Mon Sep 17 00:00:00 2001 From: "Zachary J. Rollyson" Date: Fri, 11 Jul 2025 11:48:10 -0400 Subject: [PATCH 27/27] chore(cfd): add to-do RE #136 --- runner-manager/cfd/cmd/drive/stage.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runner-manager/cfd/cmd/drive/stage.go b/runner-manager/cfd/cmd/drive/stage.go index f942d9a..91373c5 100644 --- a/runner-manager/cfd/cmd/drive/stage.go +++ b/runner-manager/cfd/cmd/drive/stage.go @@ -67,6 +67,8 @@ func (s *stage) RunSSH(guid string, cmd string) error { args := []string{"ssh", "-p 2222", "-T", "-o StrictHostKeyChecking=no"} host := fmt.Sprintf("cf:%s/0@ssh.fr-stage.cloud.gov", guid) + // TODO: can we rely on the Bash runner's .profile's edits to SSH Config? + // See: https://github.com/GSA-TTS/gitlab-runner-cloudgov/issues/136 epCfg := s.common.config.EgressProxyConfig if epCfg != (EgressProxyConfig{}) { proxy := fmt.Sprintf("-o ProxyCommand corkscrew %v %v %%h %%p %v",