Laden...

Jenkins Linting unterschiedliche Ergebnisse Python und C#

Erstellt von nieselfriem vor 2 Jahren Letzter Beitrag vor 2 Jahren 416 Views
N
nieselfriem Themenstarter:in
44 Beiträge seit 2004
vor 2 Jahren
Jenkins Linting unterschiedliche Ergebnisse Python und C#

Hallo Zusammen,

wir haben uns in Python einen Linter zusammengebaut um unsere Jenkinspipline vorab quasi über die API (https://www.jenkins.io/doc/book/pipeline/development/) auf die Richtigkeit der Syntax checken zu lassen. Das funktioniert soweit ganz gut. Nun dachte ich mir, versuchst du das ganze mal etwas komfortabler zu machen und schreibst ein kleines Tool in C# mit einer GUI. Das ging auch recht gut von der Hand.
Leider kommt die API vom Jenkins zu einem unterschiedlichen Ergebnis. Sende ich den Inhalt des Pipelinescripts über Python mit dem unteren Code, kommt zurück, dass der Code valide ist. Dies entspricht auch der Tatsache. Versuche ich das gleiche mit meinem C#-Client, meldet er Syntaxfehler, die nicht existieren.

Dazu hier mal der pythoncode


 with open(file, 'r') as jenkinsFile:
                    jenkinsfile_payload = {'jenkinsfile': jenkinsFile.read()}
                try:
                    r = requests.get(self.jenkins_base_url + '/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\":\",//crumb)',
                                     auth=HTTPBasicAuth(user, password)).text

                    crumb = r.split(':')
                    crumb_headers = {crumb[0]: crumb[1]}

                    validate = requests.post(self.jenkins_base_url + '/pipeline-model-converter/validate', headers=crumb_headers,
                                             auth=HTTPBasicAuth(user, password), data=jenkinsfile_payload)
                    validation_info = "Die Datei {} wurde geprüft: \n{}".format(file, validate.text)
                    self.outputTextField.setText(validation_info)

meine Adaption dessen in C#:


public string validate(string jenkinsBaseUrl, string[] crumb)
        {
            string url = jenkinsBaseUrl + "/pipeline-model-converter/validate";
            string content = "jenkinsfile="+pipelineContent;
            String responseString;
            var httpRequest = (HttpWebRequest)WebRequest.Create(url);
            var data = Encoding.UTF8.GetBytes(content);
            String base64String = Convert.ToBase64String(Encoding.UTF8.GetBytes("user:password"));          
            httpRequest.Headers.Add("Authorization", "Basic " + base64String);
            httpRequest.Headers.Add(crumb[0], crumb[1]);
            httpRequest.ContentType = "application/x-www-form-urlencoded";
            //httpRequest.ContentType = "text/plain";
            httpRequest.Method = "POST";
            httpRequest.ContentLength = data.Length;
            using (var requestStream = httpRequest.GetRequestStream())
            {
                requestStream.Write(data, 0, data.Length);
            }
            var httpResponse = (HttpWebResponse)httpRequest.GetResponse();
            Console.WriteLine(httpResponse.StatusCode);
            using (Stream stream = httpResponse.GetResponseStream())
            {
                StreamReader reader = new StreamReader(stream, Encoding.UTF8);
                responseString = reader.ReadToEnd();
            }
            Console.WriteLine(responseString);
            return "";
        }

Das Ergbnis wenn ich die API über C# anspreche


"Errors encountered validating Jenkinsfile:\nWorkflowScript: 137: expecting '}', found '' @ line 137, column 43.\n           deployStage == 'prod' \n                                 ^\n\n"

Wieso kommt die API vom Jenkins zu einem anderen Ergebnis, bzw. an welcher Stellschraube könnte ich drehen? Die Variante der Lineendings der Scriptdatei spielt beim Ergebnis offenbar keine Rolle

VG niesel

P
441 Beiträge seit 2014
vor 2 Jahren

Der Unterschied zwischen dem Python Script und dem C# Aufruf, zumindest was mir so auffällt:* Bei Python machst du erst einen Get Request, danach den Post.

  • Bei Python sendest du einen POST Request mit JSON Payload; bei C# einen Form Encoded Post Request mit JSON Payload

Verwende für HttpRequests besser den HttpClient (Api-Docs) anstatt des WebClient, der gilt seit einigen Jahren als depreceated.

T
2.219 Beiträge seit 2008
vor 2 Jahren

Du solltest dann auch den HttpClient disposen.
Ebenfalls auch deinen StreamReader.
ContentType sollte dann "application/json" sein.
Am besten wäre es auch, wenn du die Konsolenausgaben entfernst.
Wenn du auf falsche Codes reagieren willst, kannst du im HttpClient mit EnsureSuccessStatusCode eine Exception auslösen lassen.

Ebenfalls solltest du auf Rückgabewerte ala "" verzichten und dafür String.Empty nutzen.
Das ist sprechender und erzeugt keine unnötigen Literale.
Strings kansnt du auch durch folgendes Kosntrukt erstellen, was diese lesbarer macht.


string str1 = "Bla"
string str2 = "Blub"
string result = $"{str1} {str2}"; // "Bla Blub"

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

16.807 Beiträge seit 2008
vor 2 Jahren

Du solltest dann auch den HttpClient disposen.

Leider ist das gar keine gute Idee.
HTTP Client ist eines der ganz wenigen, wenn nicht gar einziges Beispiel in .NET, das nicht disposed werden sollte; es ist sogar kontraproduktiv den HttpClient zu disposen.
https://www.stevejgordon.co.uk/httpclient-creation-and-disposal-internals-should-i-dispose-of-httpclient
Der bessere Weg wäre aber die HttpClientFactory. Aus Logiksicht macht das aber keinen Unterschied.
Beim StreamReader gilt das nicht, der sollte disposed werden.

WebRequest ist die schlechteste Art und Weise, wie man in .NET einen Web Request machen kann, daher ist das Ding seit Jahren obsolete.
Modern kann man eine API prinzipiell mit Refit ansprechen, sodass man null Verwaltungsaufwand hat und sich alles generieren lassen kann, seit Refit 6 mit Source Code Generators.
Damit wären alle Code Issues hier (zB. vermutlich auch Encoding und Content Type) auch behoben (abgesehen von den Logik-Unterschieden zum Python Script).

Ebenfalls solltest du auf Rückgabewerte ala "" verzichten und dafür String.Empty nutzen.

Sorry, das ist totaler Quatsch 🙂 Und hat leider auch gar nichts mit dem Problem zutun; genauso wie das Console-Zeug vermutlich dem Debugging geschuldet ist und er das sicher weiß.
Technisch gesehen gibt absolut keinen Unterschied zwischen "" und string.Empty, das ist dem ILCode auch zu entnehmen.
Die Konstante ("") hat sogar den Vorteil, dass man das überall verwenden kann - string.Empty nicht. Man sollte aber lieber string statt String als Typ verwenden, um Kollisionen zu vermeiden.

string stringEmpty = String.Empty;
00007FFD4C390FBB mov rcx,1DC38553060h
00007FFD4C390FC5 mov rcx,qword ptr [rcx]
00007FFD4C390FC8 mov qword ptr [rbp+30h],rcx

string stringConstant = "";
00007FFD4C390FCC mov rcx,1DC38553060h
00007FFD4C390FD6 mov rcx,qword ptr [rcx]
00007FFD4C390FD9 mov qword ptr [rbp+28h],rcx

Bitte die Basics dazu verstehen, zB hat Nick Chapsas gibts ein YouTube Video, damit dieser Mythos irgendwann hoffentlich verschwindet 🙂
Technisch gesehen also Geschmackssache, was man verwenden will; gibts nichts zu kritisieren.
Is string.Empty actually better than "" in C#?

Bitte beachten zumindest Grundlegend beim Problem des Themas zu bleiben.

T
2.219 Beiträge seit 2008
vor 2 Jahren

@Abt
Hast Recht, wird auch in der Doku angedeutet bzw. soll man die Instanz wiederverwenden.
War mir leider nicht bewusst, wird auch nicht direkt als Hinweis/Warnung in der Doku angezeigt.
Danke

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

N
nieselfriem Themenstarter:in
44 Beiträge seit 2004
vor 2 Jahren

Hallo zusammen,

In der C# Implementierung habe ich den GET-Request weg gelassen. Dieser holt nur eine ID vom Jenins ab und das funktioniert auch soweit. Mein Problem ist tatsächlich nur der POST-Request. Für den Rest der Antworten muss ich mich noch kurz belesen.

VG niesel

309 Beiträge seit 2020
vor 2 Jahren

Du sendest im Python-Teil das Dictionary als Payload:


jenkinsfile_payload = {'jenkinsfile': jenkinsFile.read()}

Das sollte das gleiche Format als String ergeben.

Bei .NET:


string content = "jenkinsfile="+pipelineContent;

Vielleicht solltest du da mal testen ob du es auch als JSON übergeben kannst.

N
nieselfriem Themenstarter:in
44 Beiträge seit 2004
vor 2 Jahren

In Python wird das nicht als Json übergeben. Das ist ein Dictionary. Ich glaube das äquivalent in C# ist eine Map. Bin mir da aber nicht sicher. Ich bin unter .Net bisher sehr selten unterwegs.

P
441 Beiträge seit 2014
vor 2 Jahren

Am einfachsten du vergleichst die Requests und schaust dir den Unterschied an. Ein Dictionary gibt es auch in .NET.

Mit dem HttpClient sieht das ganze in C# dann auch nicht mehr so aufwendig aus, in grob etwa so:


var cl = new HttpClient();
cl.BaseAddress = new Uri("https://jenkins");
cl.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", "base64encoded");

var validationResult = await cl.PostAsync("/pipeline-model-converter/validate", new FormUrlEncodedContent(new Dictionary<string?, string?>()
{
	["jenkinsfile"] = "jenkinscontent"
}));

string result = await validationResult.Content.ReadAsStringAsync();

Edit: stimmt, mit dem json hatte ich mich in meinem Eingangsposting vertan.