@@ -731,8 +731,22 @@ func (h APIHandler) Debugger(c *gin.Context) {
731
731
return
732
732
}
733
733
defer conn .Close ()
734
+ var command []string
734
735
735
- podExecReadWriter , err := New (h .clientset , clusterName , namespace , name , c , cmd )
736
+ execCommand := []string {
737
+ "/bin/bash" ,
738
+ "-c" ,
739
+ `cd $TFO_MAIN_MODULE && \
740
+ export PS1="\\w\\$ " && \
741
+ if [[ -n "$AWS_WEB_IDENTITY_TOKEN_FILE" ]]; then
742
+ export $(irsa-tokengen);
743
+ echo printf "\nAWS creds set from token file\n"
744
+ fi && \
745
+ printf "\nTry running 'terraform init'\n\n" && bash
746
+ ` ,
747
+ }
748
+
749
+ podExecReadWriter , err := New (h .clientset , clusterName , namespace , name , c , cmd , command , execCommand , false , true )
736
750
if err != nil {
737
751
log .Printf ("Failed to connect to debug pod: %s" , err )
738
752
return
@@ -781,6 +795,55 @@ func (h APIHandler) Debugger(c *gin.Context) {
781
795
// return
782
796
}
783
797
798
+ func (h APIHandler ) UnlockTFO (c * gin.Context ) {
799
+ clusterName := c .Param ("cluster_name" )
800
+ clusterID := h .getClusterID (clusterName )
801
+ if clusterID == 0 {
802
+ c .JSON (http .StatusUnprocessableEntity , response (http .StatusUnprocessableEntity , fmt .Sprintf ("cluster_name '%s' not found" , clusterName ), nil ))
803
+ return
804
+ }
805
+ name := c .Param ("name" )
806
+ namespace := c .Param ("namespace" )
807
+ if _ , err := getResource (h .clientset , clusterName , namespace , name , c ); err != nil {
808
+ c .JSON (http .StatusUnprocessableEntity , response (http .StatusUnprocessableEntity , fmt .Sprintf ("tf resource '%s/%s' not found" , namespace , name ), nil ))
809
+ return
810
+ }
811
+
812
+ cmd := []string {}
813
+
814
+ command := []string {
815
+ "/bin/bash" ,
816
+ "-c" ,
817
+ `cd $TFO_MAIN_MODULE && \
818
+ file=$(mktemp) && \
819
+ terraform plan -no-color 2>$file
820
+ if [[ ! -s "$file" ]] ; then
821
+ echo "\nno lock detected exiting"
822
+ exit 0
823
+ fi && \
824
+ cat $file
825
+ lock=$(grep -A1 "Lock Info" $file | grep "ID") && \
826
+ lock_id=$(echo $lock | sed -n 's/.*\([0-9a-fA-F-]\{36\}\).*/\1/p') && \
827
+ echo lock=$lock && \
828
+ echo lock_id=$lock_id && \
829
+ if [ -n "$lock_id" ]; then
830
+ terraform force-unlock -force $lock_id
831
+ fi && \
832
+ echo "Done"` ,
833
+ }
834
+
835
+ execCommand := []string {}
836
+
837
+ _ , err := New (h .clientset , clusterName , namespace , name , c , cmd , command , execCommand , true , false )
838
+ if err != nil {
839
+ c .JSON (http .StatusUnprocessableEntity , response (http .StatusUnprocessableEntity , fmt .Sprintf ("tfo unlock failed: %s" , err ), nil ))
840
+ return
841
+ }
842
+
843
+ c .JSON (http .StatusOK , response (http .StatusOK , fmt .Sprint ("tfo unlocked" ), nil ))
844
+
845
+ }
846
+
784
847
type wsWrapper struct {
785
848
* websocket.Conn
786
849
}
@@ -861,7 +924,7 @@ func (t TermSizer) Next() *remotecommand.TerminalSize {
861
924
// }
862
925
863
926
// command string, argv []string, headers map[string][]string, options ...Option
864
- func New (clientset kubernetes.Interface , clusterName , namespace , name string , c * gin.Context , cmd []string ) (* PodExec , error ) {
927
+ func New (clientset kubernetes.Interface , clusterName , namespace , name string , c * gin.Context , cmd , command , execCommand []string , isUnlock , isGoroutine bool ) (* PodExec , error ) {
865
928
pty , tty , err := ptylib .Open ()
866
929
if err != nil {
867
930
log .Fatal (err )
@@ -877,12 +940,26 @@ func New(clientset kubernetes.Interface, clusterName, namespace, name string, c
877
940
SizeCh : sizeCh ,
878
941
}
879
942
880
- go func () {
881
- defer pty .Close ()
882
- err := RemoteDebug (clientset , clusterName , namespace , name , tty , c , termSizer , cmd )
943
+ if isGoroutine {
944
+ go func () {
945
+ defer pty .Close ()
946
+ err := RemoteDebug (clientset , clusterName , namespace , name , tty , c , termSizer , cmd , command , execCommand , isUnlock )
947
+ log .Println ("Pod exec exited" )
948
+ closeCh <- err
949
+ }()
950
+ } else {
951
+ err = RemoteDebug (clientset , clusterName , namespace , name , tty , c , termSizer , cmd , command , execCommand , isUnlock )
883
952
log .Println ("Pod exec exited" )
884
- closeCh <- err
885
- }()
953
+ if err != nil {
954
+ return & PodExec {
955
+ pty : pty ,
956
+ reader : pty ,
957
+ writer : pty ,
958
+ termSizer : termSizer ,
959
+ Closer : closeCh ,
960
+ }, err
961
+ }
962
+ }
886
963
887
964
return & PodExec {
888
965
pty : pty ,
@@ -930,7 +1007,7 @@ func (p *PodExec) Close() error {
930
1007
931
1008
// RemoteDebug starts the debug pod and connects in a tty that will be synced thru a websocket. Anything written to
932
1009
// stdout will be synced to the tty. stderr logs will show up in the api logs and not the tty.
933
- func RemoteDebug (parentClientset kubernetes.Interface , clusterName , namespace , name string , tty * os.File , c * gin.Context , terminalSizeQueue remotecommand.TerminalSizeQueue , cmd []string ) error {
1010
+ func RemoteDebug (parentClientset kubernetes.Interface , clusterName , namespace , name string , tty * os.File , c * gin.Context , terminalSizeQueue remotecommand.TerminalSizeQueue , cmd , command , execCommand []string , isUnlock bool ) error {
934
1011
935
1012
config , err := getVclusterConfig (parentClientset , "internal" , clusterName )
936
1013
if err != nil {
@@ -948,12 +1025,21 @@ func RemoteDebug(parentClientset kubernetes.Interface, clusterName, namespace, n
948
1025
if err != nil {
949
1026
return err
950
1027
}
1028
+ pod := generatePod (tf , command )
951
1029
952
- pod := generatePod (tf )
953
1030
pod , err = podClient .Create (c , pod , metav1.CreateOptions {})
954
1031
if err != nil {
955
1032
return err
956
1033
}
1034
+
1035
+ if isUnlock == true {
1036
+ err = getPodStatus (pod , clientset , namespace )
1037
+ if err != nil {
1038
+ return err
1039
+ }
1040
+ return nil
1041
+ }
1042
+
957
1043
defer podClient .Delete (c , pod .Name , metav1.DeleteOptions {})
958
1044
959
1045
fmt .Printf ("Connecting to %s " , pod .Name )
@@ -1001,18 +1087,6 @@ func RemoteDebug(parentClientset kubernetes.Interface, clusterName, namespace, n
1001
1087
}
1002
1088
// log.Println(file.Name())
1003
1089
// log.Println("Setting up request")
1004
- execCommand := []string {
1005
- "/bin/bash" ,
1006
- "-c" ,
1007
- `cd $TFO_MAIN_MODULE && \
1008
- export PS1="\\w\\$ " && \
1009
- if [[ -n "$AWS_WEB_IDENTITY_TOKEN_FILE" ]]; then
1010
- export $(irsa-tokengen);
1011
- echo printf "\nAWS creds set from token file\n"
1012
- fi && \
1013
- printf "\nTry running 'terraform init'\n\n" && bash
1014
- ` ,
1015
- }
1016
1090
1017
1091
if len (cmd ) > 0 {
1018
1092
execCommand = cmd
@@ -1061,6 +1135,60 @@ func RemoteDebug(parentClientset kubernetes.Interface, clusterName, namespace, n
1061
1135
return nil
1062
1136
}
1063
1137
1138
+ func getPodStatus (pod * corev1.Pod , clientset * kubernetes.Clientset , namespace string ) error {
1139
+ podStatusStartTime := time .Now ()
1140
+ for {
1141
+ pod , err := clientset .CoreV1 ().Pods (namespace ).Get (context .TODO (), pod .Name , metav1.GetOptions {})
1142
+ if err != nil {
1143
+ return err
1144
+ }
1145
+ phase := pod .Status .Phase
1146
+ if phase == "Succeeded" {
1147
+ err := deletePod (namespace , pod .Name , clientset )
1148
+ if err != nil {
1149
+ return err
1150
+ }
1151
+ return nil
1152
+ } else if phase == "Failed" {
1153
+ err := deletePod (namespace , pod .Name , clientset )
1154
+ if err != nil {
1155
+ return err
1156
+ }
1157
+ return fmt .Errorf ("Pod failed: phase %s" , phase )
1158
+ }
1159
+ time .Sleep (5 * time .Second )
1160
+ isDeleted , err := podTimeToLive (podStatusStartTime , pod .Name , namespace , clientset , 300 )
1161
+ if err != nil {
1162
+ return err
1163
+ }
1164
+ if isDeleted == true {
1165
+ return fmt .Errorf ("Pod did not complete in time and was forcefully deleted" )
1166
+ }
1167
+
1168
+ }
1169
+
1170
+ }
1171
+
1172
+ func deletePod (namespace , podName string , clientset * kubernetes.Clientset ) error {
1173
+ err := clientset .CoreV1 ().Pods (namespace ).Delete (context .TODO (), podName , metav1.DeleteOptions {})
1174
+ if err != nil {
1175
+ return err
1176
+ }
1177
+ return nil
1178
+ }
1179
+
1180
+ func podTimeToLive (podStatusStartTime time.Time , podName , namespace string , clientset * kubernetes.Clientset , timeToLive time.Duration ) (bool , error ) {
1181
+ var deleteCompleted = false
1182
+ if time .Since (podStatusStartTime ) >= timeToLive * time .Second {
1183
+ err := deletePod (namespace , podName , clientset )
1184
+ if err != nil {
1185
+ return deleteCompleted , err
1186
+ }
1187
+ deleteCompleted = true
1188
+ }
1189
+ return deleteCompleted , nil
1190
+ }
1191
+
1064
1192
// func isTerminal(file *os.File) bool {
1065
1193
1066
1194
// inFd := file.Fd()
@@ -1070,7 +1198,7 @@ func RemoteDebug(parentClientset kubernetes.Interface, clusterName, namespace, n
1070
1198
1071
1199
// }
1072
1200
1073
- func generatePod (tf * tfv1beta1.Terraform ) * corev1.Pod {
1201
+ func generatePod (tf * tfv1beta1.Terraform , command [] string ) * corev1.Pod {
1074
1202
terraformVersion := tf .Spec .TerraformVersion
1075
1203
if terraformVersion == "" {
1076
1204
terraformVersion = "1.1.5"
@@ -1230,13 +1358,17 @@ func generatePod(tf *tfv1beta1.Terraform) *corev1.Pod {
1230
1358
}
1231
1359
restartPolicy := corev1 .RestartPolicyNever
1232
1360
1361
+ if command == nil {
1362
+ command = []string {
1363
+ "/bin/sleep" , "86400" ,
1364
+ }
1365
+ }
1366
+
1233
1367
containers = append (containers , corev1.Container {
1234
1368
SecurityContext : securityContext ,
1235
1369
Name : "debug" ,
1236
1370
Image : "ghcr.io/galleybytes/terraform-operator-tftaskv1.1.0:" + terraformVersion ,
1237
- Command : []string {
1238
- "/bin/sleep" , "86400" ,
1239
- },
1371
+ Command : command ,
1240
1372
ImagePullPolicy : corev1 .PullIfNotPresent ,
1241
1373
EnvFrom : envFrom ,
1242
1374
Env : env ,
0 commit comments