{"id":37902931,"url":"https://github.com/yang-zzhong/steps","last_synced_at":"2026-01-16T17:06:17.817Z","repository":{"id":96218561,"uuid":"471958437","full_name":"yang-zzhong/steps","owner":"yang-zzhong","description":"split your logic into steps then manage it and monitor it","archived":false,"fork":false,"pushed_at":"2022-04-13T02:35:27.000Z","size":59,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-06-20T06:32:22.123Z","etag":null,"topics":["golang","k8s-operator","step","step-functions"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yang-zzhong.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-20T11:25:16.000Z","updated_at":"2022-03-20T11:57:26.000Z","dependencies_parsed_at":"2023-04-22T17:25:51.890Z","dependency_job_id":null,"html_url":"https://github.com/yang-zzhong/steps","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/yang-zzhong/steps","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yang-zzhong%2Fsteps","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yang-zzhong%2Fsteps/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yang-zzhong%2Fsteps/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yang-zzhong%2Fsteps/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yang-zzhong","download_url":"https://codeload.github.com/yang-zzhong/steps/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yang-zzhong%2Fsteps/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28480081,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","k8s-operator","step","step-functions"],"created_at":"2026-01-16T17:06:17.574Z","updated_at":"2026-01-16T17:06:17.808Z","avatar_url":"https://github.com/yang-zzhong.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Features\n\n1. save the runtime state to storage then you can retry from undone steps following the stored state\n2. generate execute report\n3. task steps management\n4. statistics for optimization of application\n5. structure your logic with steps by steps execute\n\n## Examples\n\n```go\ns := New(steps.State{Name:\"MoveToVPC\"})\n\nfunc ExecuteLogic(s *steps.Step) {\n    s.Do(\"initialize\", func(s *Step) {\n        s.Do(\"initialize CVM\", func(s *Step) {\n            if err := initCVM(); err != nil {\n                if errors.Is(err, ErrTimeout) {\n                    // not fail so you could execute logic again\n                    return\n                }\n                // failed, if you wanna retry, you should invoke s.Recover() before execute logic\n                s.Fail(err)\n                return\n            }\n            // this steps is ok.\n            s.Done()\n        })\n        s.Do(\"initialize ENIs\", func(s *Step) {\n            s.With(\"this is an optional steps, so always set it done\").Done()\n        })\n    })\n    s.Do(\"preFlight\", func(s *Step) {\n        s.Do(\"chkAccount\", func(s *Step) {\n            s.With(\"check whether account usable or not\")\n            if err := chkAccount(); err != nil {\n                s.Fail(err)\n            }\n            s.Done()\n        })\n        s.Do(\"chkNetwork\", func(s *Step) {\n            s.Info(func(info interface{}) {\n                var deadIps []string\n                if info != nil {\n                    deadIps = info.([]string)\n                }\n                deadIps, err := getDeadIps(deadIps)\n                if err != nil {\n                    s.Fail(err)\n                    return\n                }\n                s.With(deadIps)\n                if len(deadIps) == 0 {\n                    s.Done()\n                }\n            })\n        })\n    })\n    s.Do(\"inFlight\", func(s *Step) {\n        s.Do(\"migrateInstances\", func(s *Step) {\n            s.With(\"modify instances' network configuration\").Done()\n        })\n        s.Do(\"cloneENIs\", func(s *Step) {\n            s.Done()\n        })\n    })\n}\n\nExecuteLogic(s)\n\nbts, _ := json.Marshal(s.State())\n// save bts to storage\n```\n\n`bts` can save in samewhere you prefer. next time you can execute from last unfinished state.\n\n```golang\n// attempt to execute again by the last unfinished state\nvar state steps.State\njson.Unmarshal(bts, \u0026state)\n\ns := steps.New(\u0026state)\n\nExecuteLogic(s)\n```\n\nthe jsoned state is something like below\n```json\n{\n\t\"name\": \"MoveToVpc\",\n\t\"info\": \"move cvm to another vpc\",\n\t\"err\": \"Can't check network\",\n\t\"startedAt\": \"2022-03-19T16:54:44.826117+08:00\",\n\t\"doneAt\": \"2022-03-19T16:54:44.82612+08:00\",\n\t\"states\": [\n\t\t{\n\t\t\t\"name\": \"initialize\",\n\t\t\t\"info\": null,\n\t\t\t\"err\": \"\",\n\t\t\t\"startedAt\": \"2022-03-19T16:54:44.826117+08:00\",\n\t\t\t\"doneAt\": \"2022-03-19T16:54:44.826118+08:00\",\n\t\t\t\"states\": [\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"initialize CVM\",\n\t\t\t\t\t\"info\": \"MUST init CVM before migration\",\n\t\t\t\t\t\"err\": \"\",\n\t\t\t\t\t\"startedAt\": \"2022-03-19T16:54:44.826117+08:00\",\n\t\t\t\t\t\"doneAt\": \"2022-03-19T16:54:44.826117+08:00\",\n\t\t\t\t\t\"states\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"initialize ENIs\",\n\t\t\t\t\t\"info\": \"this is an optional steps, so always set it done\",\n\t\t\t\t\t\"err\": \"\",\n\t\t\t\t\t\"startedAt\": \"2022-03-19T16:54:44.826118+08:00\",\n\t\t\t\t\t\"doneAt\": \"2022-03-19T16:54:44.826118+08:00\",\n\t\t\t\t\t\"states\": []\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"name\": \"preFlight\",\n\t\t\t\"info\": null,\n\t\t\t\"err\": \"Can't check network\",\n\t\t\t\"startedAt\": \"2022-03-19T16:54:44.826118+08:00\",\n\t\t\t\"doneAt\": \"2022-03-19T16:54:44.82612+08:00\",\n\t\t\t\"states\": [\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"chkAccount\",\n\t\t\t\t\t\"info\": \"check whether account usable or not\",\n\t\t\t\t\t\"err\": \"\",\n\t\t\t\t\t\"startedAt\": \"2022-03-19T16:54:44.826119+08:00\",\n\t\t\t\t\t\"doneAt\": \"2022-03-19T16:54:44.826119+08:00\",\n\t\t\t\t\t\"states\": []\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"chkNetwork\",\n\t\t\t\t\t\"info\": \"check if network is good for migration\",\n\t\t\t\t\t\"err\": \"Can't check network\",\n\t\t\t\t\t\"startedAt\": \"2022-03-19T16:54:44.82612+08:00\",\n\t\t\t\t\t\"doneAt\": \"2022-03-19T16:54:44.82612+08:00\",\n\t\t\t\t\t\"states\": []\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}\n```\n\n## Use it with K8S Operator Reconciler\n\nThis tool is perfectly matched with management of the k8s reconcile logic. because it can reconcile many times to achieve the last goal status. it will begin from undone steps in each reconciling\n\n```golang\n//+kubebuilder:rbac:groups=udious.com,resources=stepss/status,verbs=get;update;patch\n//+kubebuilder:rbac:groups=udious.com,resources=stepss/finalizers,verbs=update\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Step object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.0/pkg/reconcile\nfunc (r *StepReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\t_ = log.FromContext(ctx)\n\tvar obj v1.YourObjType\n\tr.Get(context.Background(), okey, \u0026obj)\n\tstate := \u0026steps.State{Name: \"TestObjState\"}\n\tif obj.State != \"\" {\n\t\tif err := json.Unmarshal(obj.State, \u0026state); err != nil {\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\ts := steps.New(state)\n\ts.Do(\"step1\", func(s *steps.Step) {\n\t\t// your step1 logic\n\t})\n\ts.Do(\"step2\", func(s *steps.Step) {\n\t\t// your step2 logic\n\t})\n\ts.Do(\"step3\", func(s *steps.Step) {\n\t\t// your step3 logic\n\t})\n\ts.Do(\"step4\", func(s *steps.Step) {\n\t\t// your step4 logic\n\t})\n\ts.Do(\"step5\", func(s *steps.Step) {\n\t\t// your step5 logic\n\t})\n\tvar err error\n\tobj.State, err = json.Marshal(s.State())\n\n\tr.Update(context.Background(), obj)\n\n\treturn ctrl.Result{Requeue: s.State().Proceeding()}, nil\n}\n```\n\n## Sync async invokes\n\n```golang\n\nstate := steps.State{Name:\"sync\"}\n\nfunc invokeAsyncDo(s *steps.Step, asyncDo func() string, confirmAsyncDo func(taskId string) string) {\n    s.Info(func(info interface{}) {\n        if info == nil {\n            taskId, err := asyncDo();\n            if err != nil {\n                if isFatal(err) {\n                    s.Fail(err)\n                }\n                // retry next loop\n                return\n            }\n            s.With(taskId)\n            return\n        }\n        taskId := info.(string)\n        state := confirmAsyncDo(taskId)\n        switch state {\n        case \"Running\":\n            // reconfirm next time\n            time.Sleep(time.Second)\n        case \"Fail\":\n            s.Fail(errors.New(\"async failed\"))\n        case \"Success\":\n            s.Done()\n        }\n    })\n}\n\nfunc doTask() {\n    splitInstancesRecovers := 0\n    for !state.Done() \u0026\u0026 !state.Failed() {\n        s := steps.New(state)\n        s.Do(\"prepareInstances\", func(s *steps.Step) {\n            invokeAsyncDo(s, func() string {\n                // invoke then return taskId\n            }, func(taskId string) string {\n                // use taskId to confirm whether it's done\n            }) \n        })\n        s.Do(\"splitInstances\", func(s *steps.Step) {\n            invokeAsyncDo(s, func() string {\n                // invoke then return taskId\n            }, func(taskId string) string {\n                // use taskId to confirm whether it's done\n            }) \n        })\n        // splitIntances fail can recover\n        if state.Failed() {\n            // retry 5 times\n            if splitInstancesRecovers \u003c 5 {\n                state.Recover()\n                splitInstanceRecovers += 1\n            }\n        }\n        s.Do(\"conbineInstances\", func(s *steps.Step) {\n            invokeAsyncDo(s, func() string {\n                // invoke then return taskId\n            }, func(taskId string) string {\n                // use taskId to confirm whether it's done\n            }) \n        })\n        s.Do(\"confirmResult\", func(s *steps.Step) {\n            invokeAsyncDo(s, func() string {\n                // invoke then return taskId\n            }, func(taskId string) string {\n                // use taskId to confirm whether it's done\n            }) \n        })\n    }\n}\n\n```\n\n## Sync And Async Mix\n\n```golang\ns := New(\u0026State{Name:\"test\"})\ns.Do(\"step1\", func(s *steps.Step) {\n    s.Done()\n})\ns.Async(\"step2\", func() {\n    // concurrently execute work1 and work2\n    s.Do(\"worker1\", func(s *steps.Step) {\n        s.Done()\n    })\n    s.Do(\"worker2\", func(s *steps.Step) {\n        s.Done()\n    })\n})\n// after step2 done, execute step3\ns.Do(\"step3\", func(s *steps.Step) {\n    s.Done()\n})\n```\n\n## Performance\n\n```\ngoos: darwin\ngoarch: amd64\npkg: github.com/yang-zzhong/steps\ncpu: Intel(R) Core(TM) i5-8257U CPU @ 1.40GHz\nBenchmark_Step_Do-8         \t58594831\t        20.48 ns/op\nBenchmark_Step_DoX-8        \t57382963\t        20.71 ns/op\nBenchmark_Step_DoR-8        \t25127598\t        49.29 ns/op\nBenchmark_State_Recover-8   \t164618029\t         7.778 ns/op\nBenchmark_Step_Done-8       \t10381000\t       126.8 ns/op\nBenchmark_Step_Fail-8       \t 7258942\t       173.6 ns/op\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyang-zzhong%2Fsteps","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyang-zzhong%2Fsteps","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyang-zzhong%2Fsteps/lists"}