Go ile k9s Benzeri CLI Uygulaması Nasıl Yapılır?

Mehmet Can Tas
7 min readApr 10, 2022
K9s https://k9scli.io/

Selam, bu makalede haftasonu Go ile ilgili kendimi geliştirmek için bir proje yapmak isterken karşılaştığım paketlerden bahsedecek ve k9s benzeri bir CLI uygulamasını Go ile nasıl yapabileceğimizi sizlere aktarmaya çalışacağım.

Projenin tamamlanmış haline aşağıdaki Github adresinden ulaşabilirsiniz.

K9s Nedir?

Uzmanı olmasam da kısaca tanımlamak gerekirse k9s terminal/komut satırı üzerinden Kubernetes cluster’larınızı yönetmenizi sağlayan bir CLI uygulamasıdır. DevOps mühendisleri arasında oldukça popüler bir araç olduğunu söyleyebilirim.

Bu makaledeki amacımız ise K9s benzeri bir CLI uygulaması geliştirmek. Elbette bu makalede k8s ile ilgili bir proje yapmayacağız onun yerine bir veri kaynağından verileri listeleyip çeşitli tasarımlarla bu veriyi terminalde göreceğiz. Kullanacağımız veri kaynağı ise JSONPlaceHolder olacak. Buradan çekeceğimiz Albums ve Todos verilerini gösterebileceğimiz 2 adet sekme yer alacak. Kullanıcılar klavye üzerinde belirli tuşlar yardımıyla bu sekmeler arasında ve sekmelerin içerisindeki tabloların satırlarında geçiş yapabilecek.

https://jsonplaceholder.typicode.com/

İlk adım olarak bir klasör oluşturup main.go dosyamızı ekleyelim. Ardından ilk olarak aşağıdaki paketi projemize dahil etmemiz gerekiyor.

go get -u github.com/charmbracelet/lipgloss

Lipgloss paketi sizlere terminal üzerinde stillendirme yapmanızı sağlayan bir takım metotlar sunuyor. Bu metotlar ile kendi terminal uygulamanızı ve içerisindeki verileri istediğiniz renk ve şekilde gösterebilirsiniz.

Lipgloss paketinden biraz daha bahsedecek olursak padding, margin, width, height vb. CSS yazarken kullandığımız birçok özelliği terminal üzerinde kullanmamıza olanak sağlıyor bunun haricinde saladığı kolaylıklardan birisi de yazdığınız bir stil varsa bu stilden miras alan başka stiller yazabilmeniz.

Örneğin aşağıdaki örnekte styleB isimli stil değişkenimiz styleA’dan sadece background özelliğini aldı. Eğer siz styleB’ye farklı bir Foreground stili vermek istemiyorsanız o zaman o zatırı silip tamamen styleA’daki tüm stil özelliklerini kullanabilirsiniz.

var styleA = lipgloss.NewStyle().
Foreground(lipgloss.Color("229")).
Background(lipgloss.Color("63"))


var styleB = lipgloss.NewStyle().
Foreground(lipgloss.Color("201")).
Inherit(styleA)

İkinci eklememiz gereken paket Bubble Tea. Bubble Tea paketi de kullanıcı kompleks ya da basit terminal uygulamalar geliştirebilmemiz için bir takım yardımcı fonksiyonlar sunuyor. Bubble Tea, Elm Architecture’dan esinlenilerek ortaya çıkmış bir paket. Elm ise en basit tabiriyle şu an Redux’ta olduğu gibi state yönetimini sağlayan bir pattern olarak tanımlayabiliriz. Bubble Tea de buradan yola çıkarak CLI uygulamalarınızda state’inizi mesajlar aracılığıyla güncelleyip yönetebilmenize olanak sağlıyor.

go get -u github.com/charmbracelet/bubbletea

Bu iki paketi ekledikten sonra main.go dosyamızın içini aşağıdaki gibi değiştirelim. WithAltScreen metodu uygulamanızın farklı bir programızın full window modu vb. şekilde çalıştırmanıza olanak sağlayacak farklı bir pencerede açar. Burada ui.NewModel() şu an için hata verecektir ancak birazdan bu hatayı da gidermiş olacağız.

https://raw.githubusercontent.com/mehmetcantas/medium-cli/main/main.go

Projemize gerekli klasörleri oluşturalım. components, config, pkg ve ui isminde 3 adet klasörü projenizin ana dizinine oluşturun.

Ui klasörünün içerisine, ui.go isimli bir dosya daha oluşturalım. ui.go dosyası bizim projemiz için ana model görevini üstlenecek ve burada genel tanımlamaları ve yönetim işlemleri gerçekleştireceğiz.

Components bizim uygulamamıza ait parçaları barındıracak paket, config ise uygulamamızın varsayılan ayarlarını ve dışardan yaml,toml vb. bir dosya ile okuyup kullanabileceğimiz ayarları tutacak paket, pkg ise kullanıcının kullanabileceği klavye tuşlarını belirleyeceğimiz paket olacak. Uygulamamızın klasör yapısı şu an aşağıdaki gibi olmalıdır.

Projemize eklememiz gereken bir diğer paket ise Bubbles paketi. Bubbles, Bubble Tea ile geliştirdiğiniz uygulamalar için listeleme, spinner, paginator vb. birçok komponenti kullanmamızı sağlayan bir paket.

go get -u github.com/charmbracelet/bubbles

Bu paketi ekledikten sonra pkg klasörümüzün içerisine keys.go isimli bir dosya oluşturup içeriğini aşağıdaki gibi değiştirelim. Burada yaptığımız şey aslında kullanıcının bastığı tuşa göre ne tarz bir işlem yapacağını belirlemek. Ben örnek olarak bunları ekledim ancak siz çok daha fazlasını da buraya ekleyebilirsiniz.

Ayrıca yine pkg klasörünün içerisine helper.go isimli bir dosya oluşturalım. Bu dosyada ise kullanacağımız yardımcı fonksiyonları barındıracağız.

https://raw.githubusercontent.com/mehmetcantas/medium-cli/main/pkg/helper.go

config klasörünün altına config.go isimli bir dosya açıp içeriğini aşağıdaki gibi değiştirelim. Burada yaptığımız şey ise uygulamamızda yer alacak sekmeleri belirlemek ve bu sekmelere ait ön tanımlı değerleri oluşturmak. Örneğin Filters alanı her sekme için uygulayabileceğiniz ön tanımlı filtreleri koyabileceğiniz bir alan. Bu sayede siz her sekme için kullanacağınız filtre ne ise http isteğinize bu filtreyi ekleyebilirsiniz. Ben varsayılan olarak uygulamamız açıldığında JSONPlaceHolder’dan çektiğimiz verileri görüntülemek istediğim için Defaults içerisindeki View alanına PlaceholderView değerini verdim. Siz bu alanının değerini kendi isteğinize göre değiştirebilirsiniz. Burada tanımladığınız PlaceholderView ya da OtherView aslında section isimleri. Aynı zamanda her section içerisinde gösterilecek sekmeleri de burada belirtiyoruz.

https://raw.githubusercontent.com/mehmetcantas/medium-cli/main/config/config.go

Şimdi ui klasörümüzün içerisine screenconetxt isimli bir klasör açalım ve içerisine screencontext.go isimli bir dosya oluşturalım ardından içeriğini aşağıdaki gibi değiştirelim. Bu dosya bizim uygulamamıza ait genişlik, yükseklik vb. bilgileri barındıracak. Bu bilgileri ihtiyacımız olduğunda ScreenContext struct’ını kullanarak güncelleyebileceğiz. Örneğin kullanıcı sekmeler arasında geçiş yaptığında uygulamada o an gösterilen section içerisindeki sekmeleri buradan güncelleyebileceğiz.

https://raw.githubusercontent.com/mehmetcantas/medium-cli/main/ui/screencontext/screencontext.go

Burada durup sizlere section ve tab arasındaki farkı belirtmem gerekiyor. Section içerisinde farklı sekmeleri barındıran bir çatı ya da kapsayıcı gibi düşünebilirsiniz. Bu yüzden her bir section içerisinde birden fazla sekme yer alabilir. Ancak sekmelerin içerisinde göstereceğiniz model yapısı aynı olmalı. Örneğin, A section’ı içerisinde 3 adet sekme varsa hepsinde göstereceğiniz struct modeli aynı olmalı sadece içeriği değişmeli. Bu kurala bağlı kalındığı sürece istediğiniz kadar sekmeyi kullanıcıya gösterebilirsiniz. Eğer verinizin yapısı değişiyorsa o zaman farklı bir section oluşturmalısınız.

Şimdi de components klasörü altına tabs klasörü açıp içerisine tabs.go dosyasını oluşturalım ve içeriğini aşağıdaki gibi değiştirelim. Burada artık uygulamamızın görüntüsüyle alakalı bir takım stil çalışmalarına başlamış olduk. Lipgloss paketi altındaki Copy metodu bize hali hazırda oluşturduğumuz bir stili direkt olarak kopyalayıp kullanmamıza olanak sağlıyor. tea.Model interface’i içerisinde 3 metot (Init, Update, View) barındırıyor. Bu 3 metodu sekmelerimiz için oluşturduğumuz modeli kullanmak için implement etmemiz gerekiyor.

Init : Oluşturduğumuz komponent için ilk çalışacak metottur. Komponent render edildiğinde hiçbir şey yapmasını istemiyorsanız dönüş değeri olarak return nil diyerek dönüş yapabilirsiniz.

Update : Kullanıcının herhangi bir tuşa bastığında çalışan metottur. Burada dönüş değeri olarak model’i güncelleyebilir ya da bir komut gönderebilirsiniz.

View : Komponentin render olmuş hali. Aslında sadece string bir değer dönüyor her Update çağrısı sonrası View çalışarak komponentimiz render oluyor.

https://raw.githubusercontent.com/mehmetcantas/medium-cli/main/components/tabs/tabs.go

Yine components klasörünün altına constants ve listviewport isminde 2 klasör oluşturalım. Constants klasörünün içerisinde constants.go dosyasını oluşturalım ve içeriğini aşağıdaki gibi değiştirelim.

package constants type Dimensions struct { 
Width int
Height int
}

Listviewport isimli klasörün içerisine listviewport.go isimli bir dosya oluşturalım ve içeriğini aşağıdaki gibi değiştirelim. Burada yaptığımız işlem verilerimizi listelerken gerekecek olan stil ve utility fonksiyonlarını tanımlamak. Örneğin toplam kaç adet veri var şu an aktif seçili olan veri kaçıncısı, mouse ile scroll yaparak veriler arasında gezinme vb. işlemleri burada tanımlıyoruz.

https://raw.githubusercontent.com/mehmetcantas/medium-cli/main/components/listviewport/listviewport.go

Bu kısmı tamamladıktan sonra components klasörünün altında table isimli bir klasör oluşturup table.go isimli bir dosyayı oluşturduğumuz klasörün içinde yaratalım ve içeriğini aşağıdaki gibi değiştirelim. Bu kısımda da bizim asıl listemiz ya da diğer bir deyişle tablomuzun satır, sütun ayarlamalarını yapıyoruz. Border rengi, yazı rengi, seçili olan satırın arkaplan rengi vb. birçok işlemi burada gerçekleştiriyoruz. Ayrıca eğer hiç veri yoksa ekrana ne yazdırılacağı, tablo içerisinde hangi başlıklar yer alacak gibi ayarlamaları da yine burada yapıyoruz.

https://raw.githubusercontent.com/mehmetcantas/medium-cli/main/components/table/table.go

Artık kendi section’ımızı yaratmadan son olarak base model niteliğinde bir struct oluşturmamız gerekiyor. Bunun için components klasörünün altına section isimli bir klasör açıp içerisine section.go dosyasını oluşturalım ve içeriğini aşağıdaki gibi değiştirelim. Burada tüm section’larımızın için bir interface oluşturuyoruz. Bu interface’i oluşturacağımız tüm özel section’lar için kullanıp özelleştireceğiz. Ancak örneğin ekran boyutunun değişimiyle alakalı bir olay gerçekleşirse o zaman section’ımızda gösterdiğimiz içeriği de buna göre güncellemek için UpdateScreenContext metodu gibi temel fonksiyonları burada yazıyoruz.

https://raw.githubusercontent.com/mehmetcantas/medium-cli/main/components/section/section.go

Artık kendi özel section’ımızı oluşturup kullanmaya hazırız. Bunun için components klasörü altına placeholdersection isminde bir klasör oluşturalım. İçerisine client.go, placeholderdata.go ve placeholdersection.go isimli dosyaları oluşturalım.

client.go dosyası içerisinde aslında en temel haliyle veri çekme işlemleri gerçekleştireceğimiz http client nesnemiz yer alıyor. Siz burda örneğin k8s ya da benzeri bir araç ile çalışıyorsanız veri alışverişlerinizi bu tarz client.go dosyası gibi bir dosyada halledebilirsiniz.

https://raw.githubusercontent.com/mehmetcantas/medium-cli/main/components/placeholdersection/client.go

Placeholderdata.go dosyası ise http client ile çektiğimiz verileri parse edeceğimiz struct’ları ve bu struct içerisindeki verileri tablomuzda göstermeye uygun hale getireceğimiz bir takım metotları içerisinde barındırıyor.

https://github.com/mehmetcantas/medium-cli/blob/main/components/placeholdersection/placeholderdata.go

Son olarak placeholdersection.go ise bizim verileri çektiğimiz ve bu verileri tablomuza aktardığımız kısım. Burada section’ımızda hangi spinner’ı göstermek istediğimizi, IsLoading değerini, section tipini, tabloda veri olmadığında ne yazması gerektiğini vs. her şeyi ayarlıyoruz. GetSectionColumns metodu ise bu section içerisindeki tablolarda yer alan tablodaki başlıkları belirlememizi sağlayan metot. BuildRows ise bu tablodaki her bir satırı oluşturmamızı sağlıyor. NumRows metodu da aktif olarak gösterilen sekmedeki toplam veri sayısını belirtiyor.

https://raw.githubusercontent.com/mehmetcantas/medium-cli/main/components/placeholdersection/placeholdersection.go

Artık son aşamaya geldik :) ui klasörü altındaki ui.go dosyasının içeriğini aşağıdaki gibi değiştirebilirsiniz. Burada yaptığımız işlem kullanıcı section’lar arasında geçiş yaptıysa kullanıcıya gösterdiğimiz verileri ve sekmeleri güncellemek, kullanıcının bastığı tuşlara göre çeşitli aksiyonlar almak, section içerisindeki sekmeleri göstermek, eğer terminalin boyutu değiştiyse bununla alakalı güncellemeleri yapmak olarak özetleyebiliriz.

https://raw.githubusercontent.com/mehmetcantas/medium-cli/main/ui/ui.go

Her şey hazır artık projemizin çalıştığında neye benzediğine bakabiliriz.

Örnek projeyi GitHub üzerinden inceleyebilir ve kendi uygulamalarınıza uyarlayabilirsiniz. Elbette bu uygulama çok basit bir örnek ancak buradan yola çıkarak çok daha kompleks uygulamaları yapabilirsiniz. Her paketin her kodunu ne yazık ki anlatmak imkansız, umarım kafanızda fikir oluşmasına yardımcı olabilmişimdir. İyi kodlamalar ✌️

İletişim adresleri :

--

--